Почему в C# порядок имеет значение для статической инициализации?
Этот код имеет четко определенное поведение в C# не работает:
class Foo
{
static List<int> to = new List<int>( from ); // from is still null
static IEnumerable<int> from = Something();
}
Примечание: я не спрашиваю, как исправить этот код, поскольку я уже знал, как это сделать
Что является оправданием для этого? C# уже проверяет время выполнения, чтобы обнаружить первый доступ к статическим членам. Почему бы не распространить это на вещи для каждого члена и заставить их запускаться по требованию или, что еще лучше, компилятор определит порядок во время компиляции?
Кстати: я думаю, что тот же вопрос (или почти тот же) также имеет место для нестатических членов.
4 ответа
Я могу представить программиста в зависимости от порядка инициализации из-за побочных эффектов с другими статическими классами. Мы оба знаем, что в зависимости от побочных эффектов это плохая практика, но это не обязательно незаконно.
Рассмотрим что-то вроде этого:
class Foo
{
static string header = Bar.GetHeader();
static string version = Bar.GetVersion();
}
А также Bar.GetVersion
предполагает, что Bar.GetHeader
был вызван. Если бы компилятор мог свободно изменять порядок инициализации, программист не смог бы гарантировать порядок инициализации.
Гадкий, само собой разумеющийся, но совершенно законный. Если вы представляете эффекты второго порядка (то есть вызываемые статические методы, которые сами зависят от классов, имеющих побочные эффекты), вы видите, что компилятор не может надежно переставить что-либо, как невозможно (в общем случае) компилятору переставить порядок вызовов функций в вашем статическом конструкторе.
Инициализаторы - это просто синтаксический сахар. Компилятор помещает этот код в.cctor, когда компилирует ваш класс, и помещает их в порядке их размещения в коде.
Он не запускает никаких проверок, потому что это не имеет смысла. У вас все еще могут быть циклы инициализации, так что в любом случае это не сработает.
Я писал об этом некоторое время назад, если вам интересно:
Я думаю, что вам нужно использовать статический конструктор.
Вот так
class Foo
{
static List<int> to;
static IEnumerable<int> from;
static Foo()
{
from = Something();
to = new List<int>(from);
}
}
Что касается того, почему C# не делает этого при первом доступе, я просто не вижу необходимости в такой сложности, когда есть другие альтернативы, которые проясняют, что происходит.
C# выполняет проверки во время выполнения для обнаружения первого доступа к классу, но не переупорядочивает статическую инициализацию внутри класса.
Статические поля инициализируются сверху вниз, а затем статические конструкторы сверху вниз. Либо измените порядок ваших полей, либо создайте статический конструктор и инициализируйте поля из них.
См. Инициализаторы переменных в спецификации C# или эту статью об инициализаторах. Кроме того, связан вопрос о порядке статических конструкторов / инициализаторов в C#.