Ответы Юваля и Дэвида в основном верны; резюмируя:
- Использование неназначенной локальной переменной является вероятной ошибкой, и компилятор может ее обнаружить с небольшими затратами.
- Использование неназначенного поля или элемента массива с меньшей вероятностью является ошибкой, и труднее обнаружить условие в компиляторе. Поэтому компилятор не пытается обнаружить использование неинициализированной переменной для полей, а вместо этого полагается на инициализацию значением по умолчанию, чтобы сделать поведение программы детерминированным.
Комментатор ответа Дэвида спрашивает, почему невозможно обнаружить использование неназначенного поля с помощью статического анализа; это то, что я хочу расширить в своем ответе.
Во-первых, для любой переменной, локальной или иной, на практике невозможно точно определить , присвоена переменная или нет. Рассмотреть возможность:
bool x;
if (M()) x = true;
Console.WriteLine(x);
Вопрос "присвоено x?" эквивалентно «возвращает ли M () истину?» Теперь предположим, что M () возвращает истину, если Великая теорема Ферма верна для всех целых чисел меньше одиннадцати гаджиллионов, и ложь в противном случае. Чтобы определить, присвоено ли x определенно, компилятор должен, по сути, предоставить доказательство Великой теоремы Ферма. Компилятор не такой уж умный.
Таким образом, компилятор вместо этого реализует алгоритм, который работает быстро и переоценивает, когда локальный объект не назначен определенно. То есть, у него есть несколько ложных срабатываний, где говорится: «Я не могу доказать, что этот локальный адрес назначен», хотя мы с вами знаем, что это так. Например:
bool x;
if (N() * 0 == 0) x = true;
Console.WriteLine(x);
Предположим, N () возвращает целое число. Мы с вами знаем, что N () * 0 будет 0, но компилятор этого не знает. (Примечание: C # 2.0 компилятора сделал это знает, но я удалил эту оптимизацию, так как спецификация не говорит , что компилятор знает , что.)
Хорошо, так что мы знаем на данный момент? Для местных жителей непрактично получить точный ответ, но мы можем недорого переоценить непредназначенность и получить довольно хороший результат, который ошибается в части «заставить вас исправить вашу нечеткую программу». Это хорошо. Почему бы не сделать то же самое с полями? То есть сделать определенную проверку заданий, которая дешево переоценивает?
Ну, а сколько способов инициализировать локальный? Его можно присвоить в тексте метода. Его можно назначить в лямбде в тексте метода; эта лямбда может никогда не быть вызвана, поэтому эти присвоения не имеют значения. Или он может быть передан как «out» другому методу, после чего мы можем предположить, что он назначается, когда метод возвращается нормально. Это очень четкие точки, в которых назначается локальный объект, и они находятся прямо там в том же методе, в котором объявлен локальный . Для определения определенного назначения для местных жителей требуется только локальный анализ . Методы, как правило, короткие - намного меньше миллиона строк кода в методе - поэтому анализ всего метода выполняется довольно быстро.
А что насчет полей? Конечно, поля можно инициализировать в конструкторе. Или инициализатор поля. Или конструктор может вызвать метод экземпляра, который инициализирует поля. Или конструктор может вызвать виртуальный метод, который инициализирует поля. Или конструктор может вызвать метод в другом классе , который может быть в библиотеке , который инициализирует поля. Статические поля можно инициализировать в статических конструкторах. Статические поля могут быть инициализированы другими статическими конструкторами.
По сути, инициализатор поля может быть где угодно во всей программе , в том числе внутри виртуальных методов, которые будут объявлены в библиотеках, которые еще не были написаны :
public abstract class Bar
{
protected int x;
protected abstract void InitializeX();
public void M()
{
InitializeX();
Console.WriteLine(x);
}
}
Ошибка компиляции этой библиотеки? Если да, как BarCorp должен исправить эту ошибку? Назначив значение по умолчанию для x? Но это то, что компилятор уже делает.
Предположим, эта библиотека легальна. Если FooCorp пишет
public class Foo : Bar
{
protected override void InitializeX() { }
}
это что ошибка? Как компилятор должен это понять? Единственный способ - провести полный анализ программы, который отслеживает статику инициализации каждого поля на каждом возможном пути в программе , включая пути, которые включают выбор виртуальных методов во время выполнения . Эта проблема может быть сколь угодно сложной ; он может включать имитацию выполнения миллионов путей управления. Анализ локальных потоков управления занимает микросекунды и зависит от размера метода. Анализ глобальных потоков управления может занять часы, потому что это зависит от сложности каждого метода в программе и всех библиотек .
Так почему бы не провести более дешевый анализ, который не должен анализировать всю программу, а просто дает еще более завышенные оценки? Что ж, предложите алгоритм, который работает, чтобы не было слишком сложно написать правильную программу, которая действительно компилируется, и команда разработчиков сможет его рассмотреть. Я не знаю ни одного такого алгоритма.
Теперь комментатор предлагает «потребовать, чтобы конструктор инициализировал все поля». Это неплохая идея. На самом деле, это неплохая идея, что в C # уже есть такая функция для структур . Конструктор структуры требуется, чтобы однозначно назначить все поля к тому времени, когда ctor вернется в обычном режиме; конструктор по умолчанию инициализирует все поля значениями по умолчанию.
А как насчет занятий? А как узнать, что конструктор инициализировал поле ? Ctor может вызвать виртуальный метод для инициализации полей, и теперь мы вернулись в то же положение, в котором были раньше. У структур нет производных классов; классы мощь. Требуется ли библиотека, содержащая абстрактный класс, содержать конструктор, который инициализирует все свои поля? Как абстрактный класс узнает, какими значениями должны быть инициализированы поля?
Джон предлагает просто запретить вызов методов в ctor до инициализации полей. Итак, подытоживая, наши варианты:
- Сделайте распространенные, безопасные, часто используемые идиомы программирования незаконными.
- Проведите дорогостоящий анализ всей программы, из-за которого компиляция займет часы, чтобы найти ошибки, которых, вероятно, нет.
- Положитесь на автоматическую инициализацию значений по умолчанию.
Команда разработчиков выбрала третий вариант.