静态属性在分配后为 null



我有这个代码:

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

我得到了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();  
}

所以:

  1. 如果您在这些字段之前Globals还有其他内容,则应调查它们(如果为了简洁起见,它们被省略了(。这可能很简单,只需将Channels声明移动到类的顶部即可。
  2. 检查ChannelsData是否有任何静态引用,并跟踪这些静态引用到源。
  3. DoSomething中设置断点应该会提供返回到静态初始值设定项的堆栈跟踪。如果没有,请尝试通过调用通常反序列化new Blah(default(StreamingContext))来复制问题。

相关内容

  • 没有找到相关文章

最新更新