Если я создаю в своем классе bool, что-то вроде того bool check
, по умолчанию он имеет значение false.
Когда я создаю такой же bool в своем методе bool check
(а не в классе), я получаю сообщение об ошибке «использование неназначенной проверки локальной переменной». Зачем?
c#
language-design
local-variables
начимэ
источник
источник
Ответы:
Ответы Юваля и Дэвида в основном верны; резюмируя:
Комментатор ответа Дэвида спрашивает, почему невозможно обнаружить использование неназначенного поля с помощью статического анализа; это то, что я хочу расширить в своем ответе.
Во-первых, для любой переменной, локальной или иной, на практике невозможно точно определить , присвоена переменная или нет. Рассмотреть возможность:
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» другому методу, после чего мы можем предположить, что он назначается, когда метод возвращается нормально. Это очень четкие точки, в которых назначается локальный объект, и они находятся прямо там в том же методе, в котором объявлен локальный . Для определения определенного назначения для местных жителей требуется только локальный анализ . Методы, как правило, короткие - намного меньше миллиона строк кода в методе - поэтому анализ всего метода выполняется довольно быстро.
А что насчет полей? Конечно, поля можно инициализировать в конструкторе. Или инициализатор поля. Или конструктор может вызвать метод экземпляра, который инициализирует поля. Или конструктор может вызвать виртуальный метод, который инициализирует поля. Или конструктор может вызвать метод в другом классе , который может быть в библиотеке , который инициализирует поля. Статические поля можно инициализировать в статических конструкторах. Статические поля могут быть инициализированы другими статическими конструкторами.
По сути, инициализатор поля может быть где угодно во всей программе , в том числе внутри виртуальных методов, которые будут объявлены в библиотеках, которые еще не были написаны :
// 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() { } }
это что ошибка? Как компилятор должен это понять? Единственный способ - провести полный анализ программы, который отслеживает статику инициализации каждого поля на каждом возможном пути в программе , включая пути, которые включают выбор виртуальных методов во время выполнения . Эта проблема может быть сколь угодно сложной ; он может включать имитацию выполнения миллионов путей управления. Анализ локальных потоков управления занимает микросекунды и зависит от размера метода. Анализ глобальных потоков управления может занять часы, потому что это зависит от сложности каждого метода в программе и всех библиотек .
Так почему бы не провести более дешевый анализ, который не должен анализировать всю программу, а просто дает еще более завышенные оценки? Что ж, предложите алгоритм, который работает, чтобы не было слишком сложно написать правильную программу, которая действительно компилируется, и команда разработчиков сможет его рассмотреть. Я не знаю ни одного такого алгоритма.
Теперь комментатор предлагает «потребовать, чтобы конструктор инициализировал все поля». Это неплохая идея. На самом деле, это неплохая идея, что в C # уже есть такая функция для структур . Конструктор структуры требуется, чтобы однозначно назначить все поля к тому времени, когда ctor вернется в обычном режиме; конструктор по умолчанию инициализирует все поля значениями по умолчанию.
А как насчет занятий? А как узнать, что конструктор инициализировал поле ? Ctor может вызвать виртуальный метод для инициализации полей, и теперь мы вернулись в то же положение, в котором были раньше. У структур нет производных классов; классы мощь. Требуется ли библиотека, содержащая абстрактный класс, содержать конструктор, который инициализирует все свои поля? Как абстрактный класс узнает, какими значениями должны быть инициализированы поля?
Джон предлагает просто запретить вызов методов в ctor до инициализации полей. Итак, подытоживая, наши варианты:
Команда разработчиков выбрала третий вариант.
источник
bool x;
эквивалентнымbool x = false;
даже внутри метода ?Потому что компилятор пытается уберечь вас от ошибки.
Изменяет ли инициализация вашей переменной
false
что-либо в этом конкретном пути выполнения? Вероятно, нет, рассмотрениеdefault(bool)
в любом случае ложно, но оно заставляет вас осознавать, что это происходит. Среда .NET не дает вам получить доступ к «мусорной памяти», поскольку она инициализирует любое значение по умолчанию. Но все же представьте, что это ссылочный тип, и вы передадите неинициализированное (нулевое) значение методу, ожидающему ненулевого значения, и получите NRE во время выполнения. Компилятор просто пытается предотвратить это, принимая тот факт, что иногда это может приводить кbool b = false
выражениям.Эрик Липперт говорит об этом в своем блоге :
Почему это не относится к полю класса? Что ж, я предполагаю, что линию нужно было где-то провести, а инициализацию локальных переменных намного легче диагностировать и правильно выполнить, в отличие от полей класса. Компилятор мог бы это сделать, но подумайте обо всех возможных проверках, которые ему нужно будет выполнить (где некоторые из них не зависят от самого кода класса), чтобы оценить, инициализировано ли каждое поле в классе. Я не разработчик компиляторов, но уверен, что это будет определенно сложнее, так как есть множество случаев, которые учитываются и также должны выполняться своевременно . Для каждой функции, которую вам нужно спроектировать, написать, протестировать и развернуть, ценность реализации этого, в отличие от приложенных усилий, будет недостойной и сложной.
источник
Короткий ответ заключается в том, что код, обращающийся к неинициализированным локальным переменным, может быть надежно обнаружен компилятором с помощью статического анализа. А вот с полями дело обстоит иначе. Таким образом, компилятор применяет первый случай, но не второй.
Как объяснил Эрик Липперт, это не более чем дизайнерское решение языка 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, мы получаем исключение во время выполнения. Любой, кто потратил время на Stack Overflow, знает, что эта явная / неявная несогласованность инициализации приводит к очень многим: «Почему я получаю сообщение об ошибке« Ссылка на объект не установлена на экземпляр объекта »?» вопросы.источник
public interface I1 { string str {get;set;} }
и методint f(I1 value) { return value.str.Length; }
. Если это существует в библиотеке, компилятор не может знать, с чем будет связана эта библиотека, таким образом, было ли вызваноset
завещание доget
поля поддержки, может быть явно не инициализировано, но он должен скомпилировать такой код.f
. Он будет сгенерирован при компиляции конструкторов. Если вы оставите конструктор с полем, возможно, неинициализированным, это будет ошибкой. Также могут потребоваться ограничения на вызов методов и геттеров класса до инициализации всех полей.Хорошие ответы выше, но я подумал, что отправлю гораздо более простой / короткий ответ, чтобы люди, которым лень читать длинный (как и я).
Класс
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 }
В
{ }
символах определяют объем блока кода. Компилятор проходит по ветвям этих{ }
блоков, отслеживая материал. Он может легко определить, чтоBoo
не было инициализировано. Затем возникает ошибка.Почему существует ошибка?
Ошибка была введена для уменьшения количества строк кода, необходимых для обеспечения безопасности исходного кода. Без ошибки это выглядело бы так.
public string Foo() { string Boo; /* bla bla bla */ if(Boo == null) { return ""; } return Boo; }
Из руководства:
Ссылка: https://msdn.microsoft.com/en-us/library/4y7h161d.aspx
источник