分配

时间:2015-12-14 03:24:57

标签: c# json.net

我有这段代码:

static class Global
{
    public static readonly IChannelsData Channels = new ChannelsData();
    public static readonly IMessagesData Messages = new MessagesData();
}

我的理解是,因为这个类是静态的,所以Global.ChannelsGlobal.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);**
    }  

    ...

}

我在NullReferenceException上获得Global.Channels,我已在即时窗口中确认。让我感到困惑的是,我可以在new ChannelData()处找到断点,所以我知道静态成员正在填充 - 成功 - 在某些时候。

更多背景,评论请求:

    private Hashtable _channels;

    public ChannelsData()
    {
        _channels = new Hashtable();

        foreach(Channel channel in SlackApi.ChannelList())
        {
            _channels.Add(channel.GetHashCode(), channel);
        }
    }

感觉就像问题here类似。但是,在我的情况下,我使用JSON.NET反序列化而不是WCF 所讨论的属性是在一个单独的静态类中,而不是在同一个类中。我也无法使用在那里发布的解决方案的解决方法。

完整堆栈跟踪:

  

在C:\\ Vert \ Slack \ Channel.cs中的Vert.Slack.Channel.Init(StreamingContext上下文):第48行

错误:

  

对象引用未设置为对象的实例。

1 个答案:

答案 0 :(得分:6)

我已经能够通过以下方式重现它:

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);。据我所知,这是静态字段在使用之前无法初始化的唯一方式 - 经过一些测试后,即使使用运行时调度({{1} }),reflection dynamic(对于后者,初始化是在第一次方法调用时完成的)..

虽然您的代码可能不太明显(例如,链是否被另一个静态类引用启动)。例如,这将导致相同的问题,但不是立即明确:

GetUninitializedObject

所以:

  1. 如果在这些字段之前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声明移到类的顶部可能很简单。
  2. 检查Channels 任何静态引用,然后按照源代码进行操作。
  3. ChannelsData中设置断点应该会为堆栈跟踪返回静态初始值设定项。如果,请尝试通过调用通常被反序列化的DoSomething来复制该问题。