I have this code:
static class Global
{
public static readonly IChannelsData Channels = new ChannelsData();
public static readonly IMessagesData Messages
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.