Рассмотрим следующий источник:
static void Main(string[] args)
{
bool test;
Action lambda = () => { test = true; };
lambda();
if (test)
Console.WriteLine("Ok.");
}
Это должно скомпилировать, верно? Ну, это не так. Мой вопрос: согласно стандарту C # этот код должен компилироваться или это ошибка компилятора?
Сообщение об ошибке:
Use of unassigned local variable 'test'
Замечания: я знать, как исправить ошибку и я частично знаю, почему это происходит Тем не менее, локальная переменная назначается безоговорочно, и я думаю, что компилятор должен обратите внимание, но это не так. Интересно, почему.
Комментарий к ответам: C # позволяет объявив неназначенные переменные, и это на самом деле весьма полезно, т.е.
bool cond1, cond2;
if (someConditions)
{
cond1 = someOtherConditions1;
cond2 = someOtherConditions2;
}
else
{
cond1 = someOtherConditions3;
cond2 = someOtherConditions4;
}
Компилятор компилирует этот код должным образом, и я думаю, что оставление переменных без присмотра на самом деле делает код немного лучше, потому что:
На полях:
Это еще интереснее. Рассмотрим тот же пример в C ++:
int main(int argc, char * argv[])
{
bool test;
/* Comment or un-comment this block
auto lambda = [&]() { test = true; };
lambda();
*/
if (test)
printf("Ok.");
return 0;
}
Если вы закомментируете блок, компиляция заканчивается предупреждением:
main.cpp(12): warning C4700: uninitialized local variable 'test' used
Однако, если вы удалите комментарий, компилятор не выдаст никаких предупреждений. Мне кажется, что он способен определить, установлена ли переменная в конце концов.
Мой вопрос: согласно стандарту C #, должен ли этот код скомпилироваться или
это ошибка компилятора?
Это не ошибка.
Раздел 5.3.3.29 Спецификация языка C # (4.0) описывает определенные правила присваивания, касающиеся анонимных функций, включая лямбда-выражения. Я опубликую это здесь.
5.3.3.29 Анонимные функции
Для лямбда-выражения или выражения-анонимного метода-выражения с телом (либо блок, либо
выражение) тело:
Состояние определенного присваивания внешней переменной v перед
Тело такое же, как состояние v до expr. То есть определенный
Состояние присваивания внешних переменных наследуется из контекста
анонимная функция.Состояние определенного присваивания внешней переменной v после
expr совпадает с состоянием v перед expr.Пример
delegate bool Filter(int i); void F() { int max; // Error, max is not definitely assigned Filter f = (int n) => n < max; max = 5; DoWork(f); }
генерирует ошибку времени компиляции, так как max точно не назначен
где анонимная функция объявлена. Примерdelegate void D(); void F() { int n; D d = () => { n = 1; }; d(); // Error, n is not definitely assigned Console.WriteLine(n); }
также генерирует ошибку времени компиляции, так как присвоение n в
анонимная функция не влияет на состояние определенного присваивания n
вне анонимной функции.
Вы можете увидеть, как это относится к вашему конкретному примеру. Переменная test
специально не назначается до объявления лямбда-выражения. Он специально не назначается до выполнения лямбда-выражения. И он не назначается специально после завершения выполнения лямбда-выражения. Как правило, компилятор не считает, что переменная определенно назначена в точке ее чтения в if
заявление.
Что касается того, почему, я могу только повторить то, что я прочитал по этому вопросу, и только то, что я могу вспомнить, поскольку я не могу создать ссылку, но C # не пытается сделать это, потому что, хотя это тривиальный случай, который глаз может видеть Гораздо чаще случается, что этот тип анализа будет нетривиальным и действительно может привести к решению проблемы остановки. Поэтому C # «держит все просто» и требует от вас играть по гораздо более легко применимым и решаемым правилам.
Вы используете неназначенную переменную. Несмотря на то, что переменная фактически назначена, компилятор не может вывести ее из кода, который вы опубликовали.
В любом случае все локальные переменные должны быть инициализированы при объявлении, так что это интересно, но все же ошибочно.
Когда компилятор выполняет анализ потока управления для методов, чтобы определить, назначена ли переменная или нет, он будет смотреть только в рамках текущего метода. Эрик Липперт обсуждает это в этот блог. Теоретически, компилятор может анализировать методы, вызываемые из «текущего метода», чтобы определить, когда переменная определенно назначена.
Как я упоминал ранее, мы могли бы провести межпроцедурный анализ, но на практике это очень быстро запутывается. Вообразите сотню взаимно рекурсивных методов, которые все входят в бесконечный цикл, бросают или вызывают другой метод в группе. Разработка компилятора, который может логически выводить достижимость из сложной топологии вызовов, выполнима, но потенциально требует много работы. Кроме того, межпроцедурный анализ работает, только если у вас есть исходный код для процедур; Что делать, если один из этих методов находится в сборке, и все, с чем мы должны работать, это метаданные?
Имейте в виду, что ваш пример кода на самом деле не является единственным методом. Анонимный метод будет реорганизован в другой класс, будет создан его экземпляр, и он будет вызывать метод, который похож на ваше определение. Кроме того, компилятор должен будет проанализировать определение delegate
класс, а также определение Action
чтобы обосновать, что метод, который вы предоставили, был действительно выполнен.
Так что, хотя теоретически возможно, что компилятор знает, что переменная достижима в этом контексте, авторы компилятора сознательно выбирают не оба из-за сложности написания компилятора для него, а также (потенциально значительного) увеличения вовремя потребуется время для компиляции программ.
Фрагмент из раздела 8.3 стандарта ECMA. Переменные и параметры:
Переменная должна быть назначена до того, как ее значение может быть получено. Пример
class Test
{
static void Main() {
int a;
int b = 1;
int c = a + b; // error, a not yet assigned
}
}
приводит к ошибке времени компиляции, потому что он пытается использовать переменную a до того, как ей будет присвоено значение.
правила, регулирующие определенное назначение, определены в §12.3.
Поэтому в нем говорится, что переменная должна быть назначена до ее использования, иначе это приведет к ошибке компилятора. Поскольку вы создаете делегат и вызываете его, метод, который содержится в вызове делегата, технически неизвестен. Поэтому компилятор не будет в этом разбираться. Помните, что метод Invoke для делегата вызывается, а не фактический метод.