Подтвердить что ты не робот

Инициализаторы циклического типа

Я разрабатываю CLI для конкретного домена, и в течение последних двух недель я изучал различные угловые случаи, чтобы убедиться, что у меня есть четкое понимание того, что требуется.

Сейчас я смотрю на типовую конструкцию. Я рассматриваю следующий сценарий, о котором я не могу найти много информации о:

class C
{
    public static int Field = D.Field;
}

class D
{
    public static int Field = C.Field;
}

class TestProg
{
    static void Main()
    {
        Console.WriteLine( D.Field );
    }
}

Оба класса отмечены beforefieldinit.

Интересно, что эта программа действительно компилируется и работает на MSCLR, что дает:

0

Итак, похоже, что на практике происходит то, что триггерная точка в C..cctor для построения D игнорируется, поскольку конструкция D уже началась. Однако для меня эта программа выглядит недействительной, в том смысле, что C..cctor использует что-то до того, как она будет полностью построена.

Многие укажут, что приведенный выше сценарий бессмысленен, но для меня это является проблемой как разработчик CLI, потому что мне нужно знать, какая широта я имею в отношении циклических ссылок в инициализаторах типа.

Все, что я могу найти в ECMA-335, это:

Если вы отметили BeforeFieldInit, то метод инициализации типов запускается или когда-то раньше имеет доступ к любому статическому полю, определенному для этого типа.

Слова "выполняются при" оставляют в этом случае некоторую двусмысленность, потому что они не указывают, должен ли выполняться весь инициализатор или нужно просто начать выполнение.

Я столкнулся с комментарием, относящимся к конкретным правилам в спецификациях CLI о круговом справочном примере, но до сих пор я вообще не мог найти упоминания об этой проблеме в ECMA-335.

Итак, мои вопросы:

  • Является ли вышеприведенная программа полагающейся на поведение undefined? Или неуказанное поведение?

  • Если мой CLR отказался загружать вышеуказанную программу, будет ли он по-прежнему соответствовать требованиям?

  • Если нет, то каковы точные правила о круговых ссылках в конструкторах типов?

  • Существуют ли какие-либо допустимые полезные шаблоны проектирования, которые могут приводить к циклам в ориентированном графе инициализаторов типа программы при дисконтировании управления потоком?

4b9b3361

Ответ 1

Это ответ:

II.10.5.3.1 - II.10.5.3.3 ECMA 335 достаточно четко объясняет это и объясняет большую часть приведенного ниже поведения.

Это не ответ, но далеко не большой для комментария.

Это кажется мне показательным:

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(D.Field);
            Console.WriteLine(C.Field);
            Console.WriteLine(C.FieldX);
            Console.WriteLine(D.FieldY);
            Console.ReadLine();
        }
    }
}
class D
{
    public static int Field = C.FieldX;
    public static int FieldY = C.Field;
}


class C
{
    public static int FieldX = 5;
    public static int Field = D.Field;
}

Что надежно выводит: 5 0 5 0

С аналогичными результатами для ссылочного типа.

Я верю твоему. Итак, похоже, что на практике происходит то, что триггерная точка в C..cctor для построения D игнорируется, поскольку конструкция D уже началась ". false. Фактическое намерение" Если отмечено значение BeforeFieldInit, то метод инициализации типов запускается или когда-то раньше имеет доступ к любому статическому полю, определенному для этого типа". кажется мне, и действительно в pracice (в этом примере):

CLR распознает основные виды использования D, хочет инициализировать D, распознает D использует C, хочет инициализировать C и до начала D-инициализации действительно начинает. (см. II.10.5.3.3)

то есть C строго инициализируется до D, а любые ссылки на D будут иметь значение null/default.

В моем примере замените первые две основные строки

    static void Main(string[] args)
    {
        Console.WriteLine(C.Field);
        Console.WriteLine(D.Field);

И D сначала будет строго инициализирован, вывод

0 0 5 0

Теперь мой вопрос в том, каков ваш настоящий вопрос! т.е. у вас есть пример круговой зависимости, близкий к тому, что вам нужно ".

Также обратите внимание, что поведение здесь полностью аналогично

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(D.Field);
            Console.WriteLine(D.FieldY);
            Console.ReadLine();
        }
    }
}
class D
{
    public static int Field = FieldY;
    public static int FieldY = 5;
}

Что такое 0 5

Я чувствую, что "тот же" пример, но в рамках одной инициализации типов, т.е. вы можете полагаться на "существование" полей, но не на инициализацию, и если вы хотите больше детерминированного упорядочения, тогда пишите статические конструкторы (которые IIRC останавливает перед полем) и создает более детерминированный подход в II.10.5.3.3.