есть ли у лямбды C # и VB ** цепочка областей ** проблемы, подобные javascript? - PullRequest
4 голосов
/ 08 мая 2011

Я читал, что из-за того, как цепочка областей действия работает в javascript, если мы хотим обратиться к переменной V внутри функции F, которая не объявлена ​​в области действия F, это полезно (да с точки зрения производительности) объявить локальную переменную V2 в F, которая ссылается на V, а затем получить доступ к объекту, на который ссылаются V, через V2.

Мне интересно, относится ли эта концепция к замыканиям в C # и VB (доступ к локальным переменным в функциях через лямбды)

Public Shared Function Example()
    Dim a = 1
    Dim b = New Object
    Return Sub()
               'when we use the variables a and b from here does it have to "go up the scope chain"
           End Sub
End Function

кстати, я бы предпочел, если бы ответ не был преждевременная оптимизация - корень всего зла

Ответы [ 3 ]

7 голосов
/ 08 мая 2011

Краткий ответ: нет. .NET не нужно идти по цепочке областей видимости, чтобы найти переменные.

Длинный ответ:

Начните с этого примера:

static Func<string> CaptureArgs(int a, int b)
{
    return () => String.Format("a = {0}, b = {1}", a, b);
}

static void Main(string[] args)
{
    Func<string> f = CaptureArgs(5, 10);
    Console.WriteLine("f(): {0}", f());
    // prints f(): a = 5, b = 10
}

В методе CaptureArgs в стеке существуют a и b. Интуитивно понятно, что если мы ссылаемся на переменные в анонимной функции, возвращаем функцию и извлечение кадра стека должно удалить a и b из памяти. (Это называется проблема вверху funargs ).

C # не страдает от проблемы восходящих funargs, потому что за кулисами анонимная функция является просто причудливым синтаксическим сахаром по сравнению с классом, сгенерированным компилятором. Код C # выше превращается в:

private sealed class <>c__DisplayClass1
{
    // Fields
    public int a;
    public int b;

    // Methods
    public string <CaptureArgs>b__0()
    {
        return string.Format("a = {0}, b = {1}", this.a, this.b);
    }
}

Компилятор создает и возвращает новый экземпляр <>c__DisplayClass1, инициализирует его поля a и b из a и b, переданных в метод CaptureArgs (это фактически копирует a и b из стека в поля, существующие в куче), и возвращает его вызывающей стороне. Вызов f() - это действительно звонок <>c__DisplayClass1.<CaptureArgs>b__0().

Поскольку a и b, на которые есть ссылка в <CaptureArgs>b__0, являются полями ванили, на них может ссылаться делегат напрямую, им не требуются какие-либо особые правила объединения областей.

6 голосов
/ 08 мая 2011

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

Лямбды в C # или Visual Basic не страдают от этой проблемы.

В VB или C # компилятор точно знает , на какую переменную вы ссылаетесь в лямбда-функции, поэтому он может создать прямую ссылку на переменную. Единственное отличие состоит в том, что захваченные переменные (доступ к которым осуществляется из вложенной области видимости) должны быть преобразованы из локальной переменной в поле (в некотором объекте, также называемом замыканием).

Чтобы добавить пример - скажем, вы пишете что-то (сумасшедшее), например:

Func<Func<int>> Foo() {
  int x = 10;
  return () => {
    x++;
    return () => x;
  }
}

Это немного глупо, но демонстрирует вложенную область видимости. Переменная объявляется в одной области, устанавливается во вложенной области и читается в еще более глубокой области. Компилятор выдаст что-то вроде этого:

class Closure { 
  public Closure(int x) { this.x = x; }
  public int x;  
  public Func<int> Nested1() { 
    x++;
    return Func<int>(Nested2);
  }
  public int Nested2() { return x; }
}

Func<Func<int>> Foo() {
  var clo = new Closure(10);
  return Func<Func<int>>(clo.Nested1);
}

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

3 голосов
/ 08 мая 2011

Краткий ответ: Нет.

C # замыкания реализованы static (закрытые переменные явно известны и связаны) и не проходят через [[scope chain]], как в Javascript.

Запустите несколько тестов, чтобы успокоить свой разум, тогда просто не беспокойтесь об этом; -)

Удачного кодирования.

...