分配后,静态属性为null
我有这个代码:
static class Global { public static readonly IChannelsData Channels = new ChannelsData(); public static readonly IMessagesData Messages = new MessagesData(); }
我的理解是,因为这个类是静态的,所以Global.Channels
或Global.Messages
不可能在它们被赋予实例的情况下为null。
但是,我尝试访问该属性
public class Channel : IComparable { ... private SortedList _messages; [JsonConstructor] public Channel() { _messages = new SortedList(); } [OnDeserialized] private void Init(StreamingContext context) { **Global.Channels.RegisterChannel(this);** } ... }
我在Global.Channels
上得到一个NullReferenceException
,我已经在即时窗口中确认了。 让我感到困惑的是,我可以在new ChannelData()
点击断点,所以我知道静态成员正在填充 – 成功 – 在某些时候。
更多上下文,评论请求:
private Hashtable _channels; public ChannelsData() { _channels = new Hashtable(); foreach(Channel channel in SlackApi.ChannelList()) { _channels.Add(channel.GetHashCode(), channel); } }
感觉就像这里的问题类似。 但是,在我的情况下,我使用JSON.NET而不是WCF进行反序列化, 并且所讨论的属性位于单独的静态类中,而不是在同一个类中。 我也无法使用那里发布的解决方案的解决方法。
完整堆栈跟踪:
在C:\\ Vert \ Slack \ Channel.cs中的Vert.Slack.Channel.Init(StreamingContext上下文):第48行
并且错误:
你调用的对象是空的。
我已经能够使用以下内容重现它:
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
),reflection和 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))
来复制该问题,其中通常会对其进行反序列化。