我有这个代码:
static class Global
{
public static readonly IChannelsData Channels = new ChannelsData();
public static readonly IMessagesData Messages = new MessagesData();
}
我的理解是,由于这个类是静态的,所以Global.Channels
或Global.Messages
现在不可能为空,因为它们已经得到了一个实例。
但是,我尝试使用
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);**
}
...
}
我得到了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 进行反序列化,并且有问题的属性位于单独的静态类中,而不是在同一个类中。我也不能使用那里发布的解决方案的解决方法。
全栈跟踪:
at Vert.Slack.Channel.Init(StreamingContext context( in C:\\Vert\Slack\Channel.cs:line 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
(,反射和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))
来复制问题。