Ответ 1
Я смог воспроизвести его со следующим:
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))
, где она обычно будет десериализована.