Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Static property is null after being assigned

Tags:

c#

json.net

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.

like image 832
Matt Avatar asked Dec 14 '15 03:12

Matt


People also ask

Can a static class be null?

Static variable can only be null if it's on it's class. If you call the static variable from different class the result is NOT null.

When static class initialize in C#?

A static constructor is called automatically. It initializes the class before the first instance is created or any static members declared in that class (not its base classes) are referenced. A static constructor runs before an instance constructor.

Can static class can be inherited?

Static classes are sealed and therefore cannot be inherited. They cannot inherit from any class except Object. Static classes cannot contain an instance constructor. However, they can contain a static constructor.

What is static member in C#?

CsharpProgrammingServer Side Programming. We can define class members as static using the static keyword. When we declare a member of a class as static, it means no matter how many objects of the class are created, there is only one copy of the static member.


1 Answers

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.
like image 190
Rob Avatar answered Oct 08 '22 10:10

Rob