Lambda назначение локальных переменных

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

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

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

4 ответов


мой вопрос: согласно стандарту C#, должен ли этот код компилироваться или это ошибка компилятора?

Это не ошибка.

раздел 5.3.3.29 Спецификация Языка C# (4.0) описывает определенные правила назначения относительно анонимных функций, включая лямбда-выражения. Я опубликую его здесь.

5.3.3.29 анонимные функции

для лямбда-выражения или anonymous-method-expression expr с телом (либо блоком, либо выражение) тело:

  • определенное состояние назначения внешней переменной v перед тело такое же, как состояние v перед expr. То есть, определенно состояние присваивания внешних переменных наследуется из контекста анонимная функция.

  • определенное состояние назначения внешней переменной v после expr совпадает с состоянием v до выражение.

пример

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

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