C # — лямбда-присваивание локальных переменных

Рассмотрим следующий источник:

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

Однако, если вы удалите комментарий, компилятор не выдаст никаких предупреждений. Мне кажется, что он способен определить, установлена ​​ли переменная в конце концов.

11

Решение

Мой вопрос: согласно стандарту 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 # «держит все просто» и требует от вас играть по гораздо более легко применимым и решаемым правилам.

17

Другие решения

Вы используете неназначенную переменную. Несмотря на то, что переменная фактически назначена, компилятор не может вывести ее из кода, который вы опубликовали.

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

5

Когда компилятор выполняет анализ потока управления для методов, чтобы определить, назначена ли переменная или нет, он будет смотреть только в рамках текущего метода. Эрик Липперт обсуждает это в этот блог. Теоретически, компилятор может анализировать методы, вызываемые из «текущего метода», чтобы определить, когда переменная определенно назначена.

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

Имейте в виду, что ваш пример кода на самом деле не является единственным методом. Анонимный метод будет реорганизован в другой класс, будет создан его экземпляр, и он будет вызывать метод, который похож на ваше определение. Кроме того, компилятор должен будет проанализировать определение delegate класс, а также определение Action чтобы обосновать, что метод, который вы предоставили, был действительно выполнен.

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

2

Фрагмент из раздела 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 для делегата вызывается, а не фактический метод.

Стандарт ECMA для C #

1
По вопросам рекламы [email protected]