Объем переменных в делегате - PullRequest
8 голосов
/ 01 января 2009

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

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

int i = 1;

Там написано:

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

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

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

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

Ответы [ 6 ]

9 голосов
/ 01 января 2009

Это должен быть способ, позволяющий анонимным методам (и лямбда-выражениям) использовать локальные переменные и параметры, содержащиеся в ограниченном методе.

Временные решения: использовать разные имена для переменной или создать обычный метод.

3 голосов
/ 01 января 2009

«Закрытие», созданное анонимной функцией, несколько отличается от того, что создано в других динамических языках (я буду использовать 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 содержит ссылку на функцию.

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

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, как только дело завершится,

1 голос
/ 01 января 2009

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

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

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

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

0 голосов
/ 01 января 2009

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

using System;

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

        int i = 1;
    }
}

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

0 голосов
/ 01 января 2009

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

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

class Program
    {
        void Main()
        {
            VoidFunction t = RealFunction;
            int i = 1;
        }
        delegate void VoidFunction();
        void RealFunction() { int i = 0; }
    } 
0 голосов
/ 01 января 2009

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

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