简体   繁体   中英

Static property is null after being assigned

I have this code:

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

My understanding is that, because this class is static, it is impossible for Global.Channels or Global.Messages to be null now that they have been given an instance.

However, I try to access the property with

public class Channel : IComparable
{

    ...

    private SortedList<string, Message> _messages;

    [JsonConstructor]
    public Channel()
    {
        _messages = new SortedList<string, Message>();
    }

    [OnDeserialized]
    private void Init(StreamingContext context)
    {
        **Global.Channels.RegisterChannel(this);**
    }  

    ...

}

I get a NullReferenceException on Global.Channels , which I have confirmed in the immediate window. Further confusing me, I can hit the breakpoint at new ChannelData() , so I know the static member is being populated - successfully - at some point.

More context, comment request:

    private Hashtable _channels;

    public ChannelsData()
    {
        _channels = new Hashtable();

        foreach(Channel channel in SlackApi.ChannelList())
        {
            _channels.Add(channel.GetHashCode(), channel);
        }
    }

It feels like something similar to the problem here . However, in my situation I'm deserializing using JSON.NET and not WCF and the property in question is in a separate static class, not in the same class. I also can't use the workaround of a solution posted there.

Full stack trace:

at Vert.Slack.Channel.Init(StreamingContext context) in C:\\\\Vert\\Slack\\Channel.cs:line 48

And error:

Object reference not set to an instance of an object.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM