О неназначенных переменных

Просто любопытно, я не пытаюсь решить какие-либо проблемы.

Почему нужно назначать только локальные переменные?

В следующем примере:

class Program
{
    static int a;
    static int b { get; set; }
    static void Main(string[] args)
    {
        int c;
        System.Console.WriteLine(a);
        System.Console.WriteLine(b);
        System.Console.WriteLine(c);
    }
}

Почему a и b дает мне только предупреждение, а c дает мне ошибку?

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

        bool MyCondition = true;
        int c;
        if (MyCondition)
            c = 10;

Имеет ли он какое-либо отношение к управлению памятью?

Ответ 1

Поскольку другие переменные инициализируются значением default.

Джон Скит уже нашел несколько интересных слов по этой проблеме:

Для локальных переменных компилятор имеет хорошее представление о потоке - он может см. "чтение" переменной и "запись" переменной, и докажите (в большинстве случаев), что первая запись произойдет до первого читать.

Это не относится к переменным экземпляра. Рассмотрим простой свойство - как вы знаете, если кто-то установит его, прежде чем он его получит? Это делает его практически необоснованным для обеспечения разумных правил - так либо вы должны убедиться, что все поля были установлены в конструктор или позволить им иметь значения по умолчанию. Команда С# выбрала последняя стратегия.

и здесь соответствующая спецификация языка С#:

5.3 Определенное назначение

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

5.3.1 Первоначально назначенные переменные

Следующие категории переменных классифицируются как изначально назначены:

  • Статические переменные.

  • Переменные экземпляра экземпляров класса.

  • Переменные экземпляра изначально назначенных переменных структуры.

  • Элементы массива.

  • Параметры значения.

  • Справочные параметры.

  • Переменные, объявленные в предложении catch или foreach.

5.3.2 Первоначально не назначенные переменные

Следующие категории переменных классифицируются как изначально Unassigned:

  • Переменные экземпляра изначально неназначенных структурных переменных.

  • Параметры вывода, включая эту переменную экземпляра struct Конструкторы.

  • Локальные переменные, кроме тех, которые объявлены в предложении catch или foreach.

Ответ 2

Тим дает хороший ответ на ваш первый вопрос, но я могу добавить еще несколько деталей.

Ваш первый вопрос в основном "локальные жители должны быть определенно назначены, так почему бы не сделать то же ограничение на поля?" Тим указывает, что Джон указывает, что на самом деле это довольно сложно. С местными жителями почти всегда кристально ясно, когда локальный читается первым и когда он впервые написан. В тех случаях, когда это не ясно, компилятор может сделать разумные консервативные догадки. Но с полями, чтобы знать, когда происходит первое чтение и первая запись, вам нужно знать всевозможные вещи о том, какие методы вызывают в каком порядке.

Короче говоря, анализ локальных жителей требует локального анализа; анализ не должен проходить мимо блока, содержащего декларацию. Полевой анализ требует глобального анализа. Это много работы; проще сказать, что поля инициализируются значениями по умолчанию.

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

Ваш второй вопрос в основном "Ну, тогда, если автоматическое присвоение значений по умолчанию достаточно для полей, то почему это не так хорошо для локальных жителей?" и ответ "потому что неспособность назначить локальную переменную и случайно получить значение по умолчанию является общим источником ошибок". С# был тщательно разработан, чтобы препятствовать практике программирования, которые приводят к раздражающим ошибкам, и это один из них.

Ответ 3

CLR обеспечивает жесткую гарантию того, что локальные переменные инициализируются значением по умолчанию. Но у этой гарантии есть ограничения. Отсутствует его способность распознавать блоки областей внутри тела метода. Они исчезают после того, как компилятор переводит код в IL. Scope - это языковая конструкция, которая не имеет параллели в CLI и не может быть выражена в IL.

Вы можете видеть, что это происходит неправильно на языке, например, на VB.NET. Этот надуманный пример показывает поведение:

Module Module1
    Sub Main()
        For ix = 1 To 3
            Dim s As String
            If ix = 2 Then s = "foo"
            If s Is Nothing Then Console.WriteLine("null") Else Console.WriteLine(s)
        Next
        Console.ReadLine()
    End Sub
End Module

Вывод:

null
foo
foo

Или, другими словами, локальная переменная s была инициализирована только один раз и после этого сохраняет значение. Разумеется, это дает умение создавать ошибки. Компилятор VB.NET генерирует предупреждение для него и имеет простой синтаксис, чтобы избежать его (как новый). Управляемый язык, такой как С++/CLI, имеет такое же поведение, но вообще не генерирует диагностику. Но язык С# дает более сильную гарантию, он генерирует ошибку.

Это правило называется "определенное задание". Точные правила подробно описаны в Спецификации языка С#, глава 5.3.3

Определенная проверка присвоения имеет свои ограничения. Он хорошо работает для локальных переменных, поскольку их объем очень ограничен (частный для тела метода), и вы не можете добраться до них с помощью Reflection. Гораздо сложнее делать с полями класса, для этого требуется анализ всей программы, который может потребоваться для достижения того, что может видеть компилятор. Как код в другой сборке. Вот почему компилятор С# может только предупреждать об этом, но не может отклонить код вне права.