分配后,静态属性为null

Mat*_*att 16 c# json.net

我有这个代码:

static class Global
{
    public static readonly IChannelsData Channels = new ChannelsData();
    public static readonly IMessagesData Messages = new MessagesData();
}
Run Code Online (Sandbox Code Playgroud)

我的理解是,因为这个类是静态的,所以现在给它们一个实例是不可能的Global.Channels或者Global.Messages是null.

但是,我尝试访问该属性

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);**
    }  

    ...

}
Run Code Online (Sandbox Code Playgroud)

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

更多背景,评论请求:

    private Hashtable _channels;

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

        foreach(Channel channel in SlackApi.ChannelList())
        {
            _channels.Add(channel.GetHashCode(), channel);
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

完整堆栈跟踪:

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

并且错误:

你调用的对象是空的.

Rob*_*Rob 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

从本质上讲,执行顺序是:

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();  
}
Run Code Online (Sandbox Code Playgroud)

所以:

  1. 如果您Globals在这些字段之前还有其他内容,则应对其进行调查(如果为简洁起见而将其排除在外).将Channels声明移到类的顶部可能很简单.
  2. 检查ChannelsData用于任何静态引用,并按照那些源.
  3. 设置断点DoSomething应该为堆栈跟踪返回静态初始值设定项.如果没有,请尝试通过调用new Blah(default(StreamingContext))通常反序列化的位置来复制问题.