Почему локальные переменные требуют инициализации, а поля-нет?


Если я создаю bool в своем классе, просто что-то вроде bool check, по умолчанию false.

когда я создаю тот же bool в моем методе, bool check(вместо того, чтобы в классе), я получаю сообщение об ошибке "использование неназначенной проверки локальной переменной". Зачем?

4 140

4 ответа:

ответы Юваля и Дэвида в основном верны; подводя итог:

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

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

во-первых, для любой переменной, локальной или иной, практически невозможно определить ровно является ли переменная назначенные или неназначенные. Рассмотрим:

bool x;
if (M()) x = true;
Console.WriteLine(x);

вопрос - Икс назначен?"эквивалентно "возвращает ли M() true?"Теперь предположим, что М() возвращает true, если последняя теорема Ферма верна для всех целых чисел меньше, чем одиннадцать немеренно, и false в противном случае. Чтобы определить, является ли x определенно назначенным, компилятор должен по существу произвести доказательство последней теоремы Ферма. Компилятор не настолько умен.

так что компилятор делает вместо этого для locals реализует алгоритм, который быстро, и переоценивает когда локальный не назначен определенно. То есть, у него есть некоторые ложные срабатывания, где он говорит: "Я не могу доказать, что этот локальный назначен", хотя мы с вами знаем, что это так. Например:

bool x;
if (N() * 0 == 0) x = true;
Console.WriteLine(x);

предположим, что N() возвращает целое число. Мы с вами знаем, что N() * 0 будет 0, но компилятор этого не знает. (Примечание: компилятор C# 2.0 сделал знаю, но я удалил эту оптимизацию, так как спецификация не сказать что компилятор это знает.)

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

Ну, сколько существует способов инициализации локального? Это может быть назначается в тексте метода. Он может быть назначен в лямбде в тексте метода; эта лямбда может никогда не вызываться, поэтому эти назначения не имеют отношения. Или он может быть передан как " out " другому методу, и в этот момент мы можем предположить, что он назначен, когда метод возвращается нормально. Это очень четкие точки, в которых назначается локальный, и они прямо там, в том же методе, что локальный объявлен. Определение определенного назначения для местных жителей требуется только локальный анализ. Методы, как правило, короткие-гораздо меньше миллиона строк кода в методе-и поэтому анализ всего метода довольно быстрый.

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

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

// Library written by BarCorp
public abstract class Bar
{
    // Derived class is responsible for initializing x.
    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() { } 
}

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

так почему бы не сделать более дешевый анализ, который не должен анализировать всю программу, а просто переоценивает еще более серьезно? Ну, предложите алгоритм, который работает, что не делает его слишком трудно написать правильную программу, которая на самом деле компилируется, и команда разработчиков может рассмотреть его. Я не знаю такого алгоритма.

теперь комментатор предлагает " требовать, чтобы конструктор инициализировал все полы." Это неплохая идея. На самом деле, это такая неплохая идея, что в C# уже имеет эту функцию для структур. Конструктор структуры требуется определенно-назначить все поля к тому времени, когда ctor возвращается нормально; конструктор по умолчанию инициализирует все поля к их значениям по умолчанию.

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

Джон предлагает просто запретить вызов методов в ctor до инициализации полей. Итак, подводя итоги, наши варианты являются:

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

проектная группа выбрала третий вариант.

когда я создаю тот же bool в моем методе, bool check (вместо внутри класса), я получаю сообщение об ошибке " использование неназначенной локальной переменной проверять." Зачем?

потому что компилятор пытается предотвратить вас от совершения ошибки.

делает инициализацию переменной в false изменить что-либо в этом конкретном пути выполнения? Наверное, нет, учитывая default(bool) ложно в любом случае, но это заставляет вас быть в курсе что это происходит. Среда .NET предотвращает доступ к" мусорной памяти", поскольку она инициализирует любое значение по умолчанию. Но все же представьте, что это ссылочный тип, и вы передадите неинициализированное (нулевое) значение методу, ожидающему ненулевое значение, и получите NRE во время выполнения. Компилятор просто пытается предотвратить это, принимая тот факт, что иногда это может привести к bool b = false заявления.

Эрик Липперт говорит об этом в блоге пост:

причина, по которой мы хотим сделать это незаконным, не так много людей верю, потому что локальная переменная будет инициализирована мусор и мы хотим защитить вас от мусора. Мы делаем на самом деле автоматическая инициализация локальных объектов до значений по умолчанию. (Хотя с а языков программирования C++ нет, и весело позволит вам чтение мусора из неинициализированного локального.) Скорее это потому что наличие такого кода путь, вероятно, ошибка, и мы хотим бросить вы в яме качества; вы должны упорно трудиться, чтобы написать, что жук.

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

почему локальные переменные требуют инициализации, а поля-нет?

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

почему локальные переменные требуют инициализации?

Это не более чем дизайнерское решение языка C#, а объяснил Эрик Липперт. Среда CLR и среда .NET не требуют этого. VB.NET например, будет компилироваться только с неинициализированными локальными переменными, и в действительности среда CLR инициализирует все неинициализированные переменные до значений по умолчанию.

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

почему поля не требуют инициализации?

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

как насчет типов коллекций?

применение c#инициализации локальной переменной ограничено,что часто ловит разработчиков. Рассмотрим следующие четыре строки кода:

string str;
var len1 = str.Length;
var array = new string[10];
var len2 = array[0].Length;

вторая строка кода не компилируется, так как она пытается прочитать неинициализированную строковую переменную. Четвертая строка кода компилируется просто отлично, хотя, как array инициализирована, но только со значениями по умолчанию. Поскольку значение по умолчанию строки равно null, мы получаем исключение во время выполнения. Любой, кто провел здесь время на переполнение стека, будет знать, что это явное / неявное несоответствие инициализации приводит к большому количеству "почему я получаю "ссылка на объект не указывает на экземпляр объекта" ошибка?" вопросы.

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

класс

class Foo {
    private string Boo;
    public Foo() { /** bla bla bla **/ }
    public string DoSomething() { return Boo; }
}

свойства Boo может не были инициализированы в конструкторе. Поэтому, когда он находит return Boo; это не предположим что он был инициализирован. Это просто запрещает ошибка.

функции

public string Foo() {
   string Boo;
   return Boo; // triggers error
}

The { } символы определяют область действия блока кода. Компилятор ходит по ветвям этих { } блоки отслеживания материала. Он может легко сказать, что Boo не был инициализирован. Затем запускается ошибка.

почему существует ошибка?

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

public string Foo() {
   string Boo;
   /* bla bla bla */
   if(Boo == null) {
      return "";
   }
   return Boo;
}

от руководство:

компилятор C# не позволяет использовать неинициализированные переменные. Если компилятор обнаруживает использование переменной, которая, возможно, не была инициализирована, он генерирует ошибку компилятора CS0165. Дополнительные сведения см. В разделе поля (руководство по программированию в C#). Обратите внимание, что эта ошибка возникает, когда компилятор встречает конструкцию, что может привести к использованию неназначенной переменной, даже если ваш конкретный код не. это позволяет избежать необходимости чрезмерно сложные правила для определенного назначения.

ссылка:https://msdn.microsoft.com/en-us/library/4y7h161d.aspx