Объем переменных в делегате

Я нашел следующее довольно странное. Опять же, я в основном использовал замыкания в динамических языках, которые не должны быть подозрительными к одной и той же "ошибке". Следующее делает недопустимым компилятор:

VoidFunction t = delegate { int i = 0; };

int i = 1;

В нем говорится:

Локальная переменная с именем 'i' не может быть объявлено в этой области, поскольку означало бы "i", который уже используется в "ребенке", область для обозначения чего-то еще

Итак, это в основном означает, что переменные, объявленные внутри делегата, будут обладать областью действия объявленной функции. Не совсем то, что я ожидал. Я даже не пытался вызвать функцию. По крайней мере, Common Lisp имеет функцию, в которой вы говорите, что переменная должна иметь динамическое имя, если вы действительно хотите, чтобы она была локальной. Это особенно важно при создании макросов, которые не протекают, но что-то вроде этого было бы полезно здесь.

Итак, мне интересно, что делают другие люди, чтобы обойти эту проблему?

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

Ответ 1

Это должно быть так, чтобы анонимные методы (и lambdas) использовали локальные переменные и параметры, охваченные содержащим методом.

Обходные пути - либо использовать разные имена для переменной, либо создать обычный метод.

Ответ 2

"Закрытие", созданное анонимной функцией, несколько отличается от того, что создано на других динамических языках (в качестве примера я буду использовать Javascript).

function thing() {
    var o1 = {n:1}
    var o2 = {dummy:"Hello"}
    return function() { return o1.n++; }
}

var fn = thing();
alert(fn());
alert(fn());

Этот небольшой фрагмент javascript отобразит 1, затем 2. Анонимная функция может получить доступ к переменной o1, поскольку она существует в цепочке областей видимости. Однако анонимная функция имеет совершенно независимую область, в которой она может создать другую переменную o1 и тем самым скрыть любую другую дальше по цепочке областей видимости. Обратите также внимание на то, что все переменные во всей цепочке остаются, поэтому o2 будет продолжать существовать, сохраняя ссылку на объект до тех пор, пока fn varialbe будет содержать ссылку на функцию.

Теперь сравните с анонимными функциями С#: -

class C1 { public int n {get; set;} }
class C2 { public string dummy { get; set; } }

Func<int> thing() {
   var o1 = new C1() {n=1};
   var o2 = new C2() {dummy="Hello"};
   return delegate { return o1.n++; };
}
...
Func<int> fn = thing();
Console.WriteLine(fn());
Console.WriteLine(fn());

В этом случае анонимная функция не создает истинно независимую область действия больше, чем объявление переменной в любом другом функциональном блоке {} кода кода будет (используется в foreach, if и т.д.)

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

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

Ответ 3

Вы также получите CS0136 из кода следующим образом:

  int i = 0;
  if (i == 0) {
    int i = 1;
  }

Область 2-го объявления "i" однозначна, языки, подобные С++, не имеют никакой говядины. Но разработчики языка С# решили запретить это. Учитывая приведенный выше фрагмент, вы думаете, что все еще думаете, что это плохая идея? Бросьте кучу дополнительного кода, и вы можете некоторое время смотреть на этот код и не видеть ошибку.

Обходной путь тривиальный и безболезненный, просто придумайте другое имя переменной.

Ответ 4

Это потому, что делегат может ссылаться на переменные вне делегата:

int i = 1;
VoidFunction t = delegate { Console.WriteLine(i); };

Ответ 5

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

Вот обходной путь:

class Program
    {
        void Main()
        {
            VoidFunction t = RealFunction;
            int i = 1;
        }
        delegate void VoidFunction();
        void RealFunction() { int i = 0; }
    } 

Ответ 6

Собственно, ошибка, похоже, не имеет ничего общего с анонимными делегатами или выражениями lamda. Если вы попытаетесь скомпилировать следующую программу...

using System;

class Program
{
    static void Main()
    {
        // Action t = delegate
        {
            int i = 0;
        };

        int i = 1;
    }
}

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