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:
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. ChannelsData
for any static references, and follow those to the source. 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.