Почему локальная переменная С# должна быть назначена напрямую, даже если это значение по умолчанию?

Если вы посмотрите на следующий пример:

public void TestLocalValuesAssignment()
{
    int valueVariable; // = default(int) suits fine
    string refType; // null suits fine as well

    try
    {
        valueVariable = 5;
        refType = "test";
    }
    catch (Exception){}

    Console.WriteLine("int value is {0}", valueVariable);
    Console.WriteLine("String is {0}", refType);
}

вы могли легко увидеть, что переменные valueVariable и refType могли быть не назначены до их использования в Console.WriteLine(). Об этом нам сообщает компилятор с ошибками:

Error   1   Use of unassigned local variable 'valueVariable'
Error   2   Use of unassigned local variable 'refType'  

Это широко распространенный случай, и есть множество ответов о том, как это исправить (возможно исправления с комментариями).

Чего я не могу понять, так это почему такое поведение существует? Чем здесь локальные переменные отличаются от полей класса, где последние получают значение по умолчанию, если не назначены (нулевое значение для ссылочных типов и соответствующее значение по умолчанию для типов значений)? Может быть, есть пример или крайний случай, объясняющий, почему выбрано такое поведение компилятора?


person antonv    schedule 18.01.2014    source источник
comment
возможный дубликат Инициализация полей экземпляра по сравнению с локальными переменными   -  person Tim Schmelter    schedule 19.01.2014
comment
Это не поведение, это правило. Правило определенного присваивания гласит, что переменная должна быть присвоена до ее использования. Очевидно, что его не будет в вашем фрагменте кода, когда возникнет исключение. Да, одно может случиться.   -  person Hans Passant    schedule 19.01.2014
comment
@TimSchmelter Полностью согласен, это дубликат. Джон Скит предоставил довольно разумное объяснение, которое можно рассматривать как ответ на мой вопрос.   -  person antonv    schedule 19.01.2014


Ответы (4)


в принципе - так решила MS.

Если вам нужно больше, вы можете прочитать здесь и проверить Блог Эрика Липперта

Причина, по которой это недопустимо в C#, заключается в том, что использование неназначенного локального объекта с высокой вероятностью может быть ошибкой.

person Mzf    schedule 18.01.2014
comment
Эта ссылка ведет к отличному ответу Эрика Липперта. - person Ben Voigt; 19.01.2014
comment
Согласен, ответ Эрика Липперта многое объясняет. По сути, это правило предотвращает ошибки программистов. - person antonv; 19.01.2014
comment
@ Антонио: Верно. По сути, идея заключается в том, что (1) неинициализированные переменные могут быть ошибками, и (2) очень легко обнаружить, когда локальная переменная не назначена определенно, но гораздо сложнее обнаружить, когда поле не назначено определенно. . В результате действует правило: местные жители должны быть обязательно назначены. Если бы найти неназначенные поля было бы дешево и легко, это правило распространялось бы и на поля, но это недешево и не просто. - person Eric Lippert; 19.01.2014

Это описано в спецификации С#:

5.1.7 Локальные переменные

Локальная переменная, представленная объявлением-локальной-переменной, не инициализируется автоматически и поэтому не имеет значения по умолчанию. В целях проверки определенного назначения локальная переменная, представленная объявлением-локальной-переменной, считается изначально неназначенной. Объявление-локальной-переменной может включать в себя инициализатор-локальной-переменной, и в этом случае переменная считается определенно назначенной только после инициализирующего выражения (§5.3.3.4).

В пределах области действия локальной переменной, представленной объявлением-локальной-переменной, ошибкой времени компиляции является ссылка на эту локальную переменную в текстовой позиции, предшествующей ее локальной-переменной-. декларатор. Если объявление локальной переменной является неявным (§8.5.1), также будет ошибкой ссылаться на переменную в ее объявлении-локальной-переменной.

person MarcinJuraszek    schedule 18.01.2014
comment
Да, хорошая ссылка. Он ясно объясняет поведение (или правило) компилятора. Хотя это не объясняет, почему было принято такое решение. Я надеялся получить пример, где было бы очевидно, почему это правило существует. - person antonv; 19.01.2014

Когда вы делаете что-то, что кажется глупым, например, читаете переменную, которой вы никогда не присваивали значение, компилятор может сделать две вещи:

  1. Дайте вам диагностику, обращающую ваше внимание на то, что, вероятно, является ошибкой.
  2. Сделайте что-нибудь произвольное.

Поскольку вариант № 1 помогает вам находить ошибки, он предпочтительнее, особенно когда обходной путь для сообщения компилятору «Нет, я имею в виду использовать исходное значение по умолчанию» так же прост, как добавление = 0, = null или = default(T).

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

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

person Ben Voigt    schedule 18.01.2014

На самом деле ваш код должен быть в порядке, но, строго говоря, существует путь кода, который может оставить ваши переменные неназначенными перед использованием. Блок try создает возможность того, что код внутри блока не будет выполняться (если возникнет исключение), но все равно будет выполняться код за пределами catch (поскольку в нем нет ничего в catch, например return или throw, чтобы предотвратить выполнение остальной части вашего метода, если в try< возникнет исключение. /сильный>).

Если вы имеете в виду разницу между инициализацией полей "struct" и инициализацией полей класса, например:

public class A
   {
   }

       MyMethod()
       {
           int myInt; // Initialized to zero, yes, but not yet assigned.
                      // An error to use this before assigning it.

           A myA;  // defaults to null, which may be a valid initial state, but still unassigned.
                   // Also an error to use this before assigning it.

           A oneMoreA = null; // Same value as default, but at least intention is clear.
           A anotherA = new A(); // What is or is not happening in the constructor is a separate issue.
                                 // At least anotherA  refers to an actual instance of the class.
person Zenilogix    schedule 18.01.2014