Как поймать или пометить потенциальные проблемы из-за порядка инициализации статического поля

Рассмотрим следующий код С#:

using System;
class Program
{
    static string string1 = "AAA";
    static string string2 = string1 + string3;
    static string string3 = "BBB";

    static void Main()
    {
        Console.WriteLine(string2);
    }
}

Я написал код такой, как сегодня, и ожидал, что string2 будет содержать значение AAABBB, но вместо этого он просто содержал AAA. Я сделал некоторое чтение о порядке инициализации статических переменных, но мне кажется предпочтительнее, чтобы во время компиляции был сформирован некоторый тип предупреждения или ошибки.

Два вопроса:

  • Почему такой код разрешен для компиляции? (и если ответ таков: "потому что, как написано спецификацию С#", то почему это было написано именно так? Существуют ли причины, по которым я не понимаю, почему это не всегда всегда просто бросает ошибку времени компиляции?)

  • Есть ли способ получить предупреждение о компиляции или какой-либо другой флаг, если я в дальнейшем нечаянно напишу этот код в будущем?

Ответы

Ответ 1

Для вопроса 2:

Такие инструменты, как ReSharper, поймают эти вещи. В Visual Studio может быть настройка, которую вы можете включить для получения более подробного вывода компиляции.

enter image description here

Здесь код после очистки Reshaper, который производит "AAABBB"

class Program
    {
        private const string String1 = "AAA";
        private const string String2 = String1 + String3;
        private const string String3 = "BBB";

        static void Main()
        {
            Console.WriteLine(String2);
            Console.ReadLine();

        }

    }

В качестве побочного примечания, я бы сказал, поскольку мы обычно читаем сверху вниз, кажется логичным, что инициализация происходит так же, как описано в 10.4.5.1 спецификации С#.

Ответ 2

Если вы думаете о своих статических инициализаторах как о логической части статического конструктора, все имеет смысл. В вашем случае это так, как если бы вы написали:

private static string String1;
private static string String2;
private static string String3;

static Program()
{
    String1 = "AAA";
    String2 = String1 + String3;
    String3 = "BBB";
}

Причина, по которой статические инициализаторы не работают так, как вы хотите, состоит в невозможности в общем случае компилятора переупорядочить вещи. В этом случае и во многих других. Но если вы рассматриваете эффекты второго порядка, о которых я упоминал в своем ответе на аналогичный вопрос, компилятор не может надежно переупорядочить что-либо.

Было бы крайне сложно, если бы компилятор сделал это переупорядочивание "иногда".

Ответ 3

Что касается №1: причина, по которой код разрешен для компиляции, заключается в том, что в соответствии с 10.4.5.1 спецификации С# "инициализаторы статической переменной поля класса соответствуют последовательности назначений, которые выполняются в текстовый порядок, в котором они появляются в объявлении класса." Это означает, что ваша переменная string2 недвусмысленно инициализируется на "AAA" + null. Возможно, вы должны хотя бы получить предупреждение... о том, почему ваша IDE решила не предупреждать вас, я не знаю.

Что касается №2: я не знаю. Это похоже на вопрос, который был бы более подходящим, если бы также был помечен вашей IDE.

Ответ 4

Это дизайн.

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

С# имеет политику definite assignment, которая означает, что, например, поля инициализируются автоматически. Поэтому ваш string3 автоматически инициализируется на null, и поэтому, что касается компилятора, оно уже имеет значение.

Это означает, что вывод string2 равен string1 + null (это просто string2), и поэтому нет никаких причин для компилятора делать какие-либо ошибки (хотя я вижу, как было бы полезно использовать это предупреждение).