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

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

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

int i = 1;

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

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

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

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

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


person Anders Rune Jensen    schedule 01.01.2009    source источник


Ответы (6)


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

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

person Arjan Einbu    schedule 01.01.2009
comment
Но переменная i объявляется после делегата. На самом деле не очень помогает то, что компилятор говорит мне, что я не могу объявлять переменные с одинаковыми именами в этой области. Я мог бы даже не вызывать функцию в этой области. - person Anders Rune Jensen; 01.01.2009
comment
У делегата не больше независимой области действия, чем у блока кода if или foreach. Подробности смотрите в моем ответе. - person AnthonyWJones; 01.01.2009
comment
Объявляете ли вы переменную i до или после делегата, не имеет значения. При компиляции в IL переменная i становится локальной переменной, размещаемой в стеке во время вызова функции. Он не начинает существовать в середине функции. (Конечно, если делегат или лямбда-выражение использует i, то оно компилируется в поле класса, а не в локальную переменную, но это отдельная тема.) - person Timwi; 08.07.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 содержит ссылку на функцию.

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

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

person AnthonyWJones    schedule 01.01.2009
comment
Спасибо, но зачем им вообще создавать такие ужасные правила области действия? В каком случае имеет смысл блокировать локальные переменные области видимости, создавая внешние переменные с тем же именем? Это должно быть самое большее предупреждение? - person Anders Rune Jensen; 01.01.2009
comment
Это хороший вопрос, и я не уверен, что знаю ответ. Вероятно, это связано с производительностью и способностью компилятора оптимизировать. Возможно, вы можете спросить об этом конкретно в SO. - person AnthonyWJones; 01.01.2009
comment
Хорошая идея, заданная здесь: stackoverflow.com/questions/405116/ - person Anders Rune Jensen; 01.01.2009

Вы также получите CS0136 из такого кода:

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

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

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

person Hans Passant    schedule 01.01.2009
comment
Вот почему у нас есть предупреждения, не так ли? - person Anders Rune Jensen; 01.01.2009
comment
Хм, предупреждения... Вам тоже нравится параметр /Wx компилятора C/C++? Разработчик языка, который говорит «нет» вместо «может быть», получает мой голос. - person Hans Passant; 01.01.2009
comment
В этом случае да. Потому что я считаю случай с делегатом ошибкой языка, поскольку он заставляет меня писать неоптимальный код только для того, чтобы сделать компилятор счастливым. Язык должен работать с вами, а не против вас... - person Anders Rune Jensen; 01.01.2009
comment
Просто для ясности, я думаю, что ваш пример, вероятно, должен генерировать ошибку компиляции, в то время как для примера, опубликованного в вопросе, я все еще твердо придерживаюсь своего последнего комментария :) - person Anders Rune Jensen; 01.01.2009
comment
Ну, это нормально. Но вы спорите с куском кремния, он не слушает и не возражает. Опубликовать на connect.microsoft.com - person Hans Passant; 01.01.2009

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

int i = 1;
VoidFunction t = delegate { Console.WriteLine(i); };
person dalle    schedule 01.01.2009

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

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

class Program
    {
        void Main()
        {
            VoidFunction t = RealFunction;
            int i = 1;
        }
        delegate void VoidFunction();
        void RealFunction() { int i = 0; }
    } 
person Øyvind Skaar    schedule 01.01.2009

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

using System;

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

        int i = 1;
    }
}

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

person Community    schedule 01.01.2009