我已经能够使用以下内容重现它:
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; } }
从本质上讲,执行顺序是:
var m = Global.Messages;
导致静态初始化程序运行Global
.
根据ECMA-334关于静态场初始化:
类声明的静态字段变量初始值设定项对应于以它们出现在类声明中的文本顺序执行的赋值序列 .如果类中存在静态构造函数(第17.11节),则在执行该静态构造函数之前立即执行静态字段初始值设定项.否则,静态字段初始化器在第一次使用该类的静态字段之前的实现相关时间执行
这是根本原因.有关循环引用的更多上下文,请参阅注释
这实际上意味着我们在初始化程序有机会完成设置之前进行调用Deserialize
和命中Global.Channels.DoIt(this);
.据我所知,这是静态字段在被使用之前无法初始化的唯一方法 - 在一些测试之后,即使使用运行时调度(dynamic
),反射和 GetUninitializedObject
(对于后者,初始化是在第一个方法调用上完成,但是)..
虽然您的代码可能不太明显(例如,链是否被另一个静态类引用启动).例如,这将导致相同的问题,但不是立即明确:
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(); }
所以:
如果您Globals
在这些字段之前还有其他内容,则应对其进行调查(如果为简洁起见而将其排除在外).将Channels
声明移到类的顶部可能很简单.
检查ChannelsData
用于任何静态引用,并按照那些源.
设置断点DoSomething
应该为堆栈跟踪返回静态初始值设定项.如果没有,请尝试通过调用new Blah(default(StreamingContext))
通常反序列化的位置来复制问题.