Static property is null after being assigned

后端 未结 1 726
栀梦
栀梦 2021-02-12 16:37

I have this code:

static class Global
{
    public static readonly IChannelsData Channels = new ChannelsData();
    public static readonly IMessagesData Messages         


        
1条回答
  •  暖寄归人
    2021-02-12 17:06

    I've been able to reproduce it with the following:

    class Program
    {
        static void Main(string[] args)
        {
            var m = Global.Messages;
        }
    }
    [Serializable]
    public class Blah
    {
        [OnDeserialized]
        public void DoSomething(StreamingContext context)
        {
            Global.Channels.DoIt(this);
        }
    }
    static class Global
    {
        private static Blah _b = Deserialize();
    
        public static readonly IChannelsData Channels = new ChannelsData();
        public static readonly IMessagesData Messages = new MessagesData();
    
        public static Blah Deserialize()
        {
            var b = new Blah();
            b.DoSomething(default(StreamingContext));
            return b;
        }
    }
    

    Essentially, the order of execution is:

    var m = Global.Messages; causes the static initializer to run for Global.

    According to ECMA-334 regarding static field initialization:

    The static field variable initializers of a class declaration correspond to a sequence of assignments that are executed in the textual order in which they appear in the class declaration. If a static constructor (§17.11) exists in the class, execution of the static field initializers occurs immediately prior to executing that static constructor. Otherwise, the static field initializers are executed at an implementation-dependent time prior to the first use of a static field of that class

    This is the root cause. See the comments for more context on the circular reference

    This essentially means that we're calling Deserialize and hitting Global.Channels.DoIt(this); before the initializer has a chance to finish setting up. As far as I'm aware, this is the only way a static field cannot be initialized before being used - after some testing, they are indeed created even when using run-time dispatches (dynamic), reflection and GetUninitializedObject (for the latter, initialization is done on the first method call, however)..

    Though your code may be less obvious to diagnose (for example, if the chain is kicked off by another static class referencing). For example, this will cause the same issue but is not as immediately clear:

    class Program
    {
       static void Main(string[] args)
       {
           var t = Global.Channels;
       }
    }
    [Serializable]
    public class Blah
    {
       [OnDeserialized]
       public void DoSomething(StreamingContext context)
       {
           Global.Channels.DoIt();
       }
    }
    
    public interface IChannelsData { void DoIt(); }
    class ChannelsData : IChannelsData
    {
        public static Blah _b = Deserialize();
        public static Blah Deserialize()
        {
            var b = new Blah();
            b.DoSomething(default(StreamingContext));
            return b;
        }
        public void DoIt() 
        {
            Console.WriteLine("Done it");
        }
    }
    
    static class Global
    {
        public static readonly IChannelsData Channels = new ChannelsData();
        public static readonly IMessagesData Messages = new MessagesData();  
    }
    

    So:

    1. If you have anything else in Globals before those fields, you should investigate them (if they were left out for brevity). It may be simple as moving the Channels declaration to the top of the class.
    2. Inspect ChannelsData for any static references, and follow those to the source.
    3. Setting a breakpoint in DoSomething should give you a stack trace back to the static initializers. If it doesn't, try to replicate the issue by invoking new Blah(default(StreamingContext)) where it would usually be deserialised.

    0 讨论(0)
提交回复
热议问题