Не понимаю поведение List <Func <int, int >> в цикле for - PullRequest
0 голосов
/ 03 июля 2018

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

Итак, у нас есть эти два цикла for:

List > list = new List > ();

  for (int i = 0; i < 5; i++)
  {
    list.Add(j => j + i);
  }
  for (int i = 0; i < 5; i++)
  {
    Console.WriteLine(list[i](i));
  }

Я ожидал, что вывод будет примерно таким:

0 (поскольку j + 0 с j = 0 равно 0)

2 (поскольку j + 1 с j = 1 равно 2)

4 (…)

6

8

Вместо этого вывод показал:

5 (подозреваю, поскольку j + 5 с j = 0 равно 5)

6 (подозреваю, поскольку j + 5 с j = 1 равно 6)

7 (…)

8

9

В результате добавления Funcs к списку значение i обновляется для каждого ранее добавленного Func.

Почему это так?

Ответы [ 4 ]

0 голосов
/ 03 июля 2018

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

Компилятор поднимает локальную переменную как поле сгенерированного класса, в основном происходит что-то похожее на это:

class Closure
{
    public int i;
    public int Fn(int j) => i + j;
}
static void Main(string[] args)
{
    List<Func<int, int>> list = new List<Func<int, int>>();
    var c = new Closure();
    for (c.i = 0; c.i < 5; c.i++)
    {
        list.Add(c.Fn);
    }
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine(list[i](i));
    }
}

Вы можете обойти это поведение, объявив локальную переменную в циклах: o компилятор захватит эту локальную переменную, и, поскольку семантика этой переменной заключается в том, что переменная должна отличаться на каждой итерации цикла, компилятор сгенерирует отдельную переменную. захватить экземпляр для каждой итерации.

for (int i = 0; i < 5; i++)
{
    int l = i;
    list.Add(j => j + l);
}
// Equivalent to :

for (var i = 0; i < 5; i++)
{
    var c = new Closure();
    c.i = i;
    list.Add(c.Fn);
}
0 голосов
/ 03 июля 2018

Это очень написано о проблеме захвата / закрытия

for (int i = 0; i < 5; i++)
{
   var newI = i;
   list.Add(j => j + newI);
}
for (int i = 0; i < 5; i++)
{
   Console.WriteLine(list[i](i));
}

выход

0
2
4
6
8
0 голосов
/ 03 июля 2018

Это известный эффект захвата ; в вашем текущем коде

for (int i = 0; i < 5; i++)
{
    // each lambda uses shared "i" variable
    list.Add(j => j + i);
}

// Now (after the for loop) i == 5, 
// that's why all lambdas j => j + i are in fact j => j + 5

Если вы хотите избежать i захвата переменных, вы можете изменить свой код на

for (int i = 0; i < 5; i++)
{
    // local variable: each iteration has its own temp to be captured
    int temp = i;

    list.Add(j => j + temp);
}

//  1st lambda j => j + temp equals to j => j + 0
//  2nd lambda j => j + temp equals to j => j + 1
// ...
// n-th lambda j => j + temp equals to j => j + n - 1
0 голосов
/ 03 июля 2018

Это потому, что локальная переменная i захватывает только последнее значение. Вам следует создать отдельную переменную с локальной областью действия (которая выходит из области действия для следующей итерации):

for (int i = 0; i < 5; i++)
{
    int l = i;
    list.Add(j => j + l);
}
...