Внешняя переменная ловушка - PullRequest
46 голосов
/ 05 августа 2010

Что такое внешняя переменная ловушка? Объяснение и примеры в C # приветствуются.

РЕДАКТИРОВАТЬ: Включение диктата Джона Скита :)

Эрик Липперт в ловушке внешней переменной

Ответы [ 5 ]

65 голосов
/ 05 августа 2010

«Ловушка внешней переменной» возникает, когда разработчик ожидает, что значение переменной будет захвачено лямбда-выражением или анонимным делегатом, когда фактически переменная захвачена сама.

Пример:

var actions = new List<Action>();
for (var i = 0; i < 10; i++)
{
    actions.Add(() => Console.Write("{0} ", i));
}
foreach (var action in actions)
{
    action();
}

Возможный вывод № 1:

0 1 2 3 4 5 6 7 8 9

Возможный вывод № 2:

10 10 10 10 10 10 10 10 10 10

Если вы ожидали выхода # 1, вы попали в ловушку внешней переменной. Вы получите вывод № 2.

Исправлено:

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

var actions = new List<Action>();
for (var i = 0; i < 10; i++)
{
    var j = i;
    actions.Add(() => Console.Write("{0} ", j));
}
foreach (var action in actions)
{
    action();
}

Подробнее см. Также Блог Эрика Липперта .

4 голосов
/ 05 августа 2010

Что-то вроде

foreach (var s in strings)
    var x = results.Where(r => (r.Text).Contains(s));

Не даст ожидаемых результатов, поскольку Contains не выполняется для каждой итерации.Присвоение s временной переменной внутри цикла исправит это.

2 голосов
/ 28 октября 2016

Стоит отметить, что эта ловушка существовала и для циклов foreach, но была изменена начиная с C # 5.0, то есть внутри foreach замыкания циклов теперь закрываются каждый раз над свежей копией переменной цикла , Итак, код ниже:

var values = new List<int>() { 100, 110, 120 };
var funcs = new List<Func<int>>();
foreach (var v in values)
    funcs.Add(() => v);
foreach (var f in funcs)
    Console.WriteLine(f());

Печать 120 120 120 , но 100 110 120 > = C # 5.0

Однако for циклы по-прежнему ведут себя так же.

1 голос
/ 05 августа 2010

@ dtb является правильным (большое +1), но важно отметить, что это применимо, только если область действия замыкания выходит за пределы цикла. Например:

var objects = new []
    {
        new { Name = "Bill", Id = 1 },
        new { Name = "Bob", Id = 5 },
        new { Name = "David", Id = 9 }
    };

for (var i = 0; i < 10; i++)
{
    var match = objects.SingleOrDefault(x => x.Id == i);

    if (match != null)
    {
        Console.WriteLine("i: {0}  match: {1}", i, match.Name);
    }
}

Это напечатает:

i: 1  match: Bill
i: 5  match: Bob
i: 9  match: David

ReSharper предупредит о «Доступе к измененному закрытию», который в этом случае можно смело игнорировать.

0 голосов
/ 06 августа 2010

Эта статья, объясняющая концепцию замыканий, полезна:

http://en.wikipedia.org/wiki/Closure_(computer_science)

Кроме того, эта статья действительно хороша из более конкретной реализации C #:

http://blogs.msdn.com/b/abhinaba/archive/2005/08/08/448939.aspx

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...