Вопрос о foreach и делегатах - PullRequest
       21

Вопрос о foreach и делегатах

5 голосов
/ 21 октября 2009

Предположим, следующий код:

foreach(Item i on ItemCollection)
{
   Something s = new Something();
   s.EventX += delegate { ProcessItem(i); };
   SomethingCollection.Add(s);
}

Конечно, это неправильно, потому что все делегаты указывают на один и тот же пункт. Альтернатива:

foreach(Item i on ItemCollection)
{
   Item tmpItem = i;
   Something s = new Something();
   s.EventX += delegate { ProcessItem(tmpItem); };
   SomethingCollection.Add(s);
}

В этом случае все делегаты указывают на свой пункт.

А как насчет этого подхода? Есть какое-нибудь другое лучшее решение?

Ответы [ 5 ]

11 голосов
/ 21 октября 2009

ОБНОВЛЕНИЕ: Здесь есть обширный анализ и комментарии по этому вопросу:

http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/


Это очень часто сообщаемая проблема; обычно это сообщается как ошибка компилятора, но на самом деле компилятор делает правильные вещи в соответствии со спецификацией. Анонимные функции закрывают переменных , а не значений , и существует только одна переменная цикла foreach. Поэтому каждая лямбда закрывается по одной и той же переменной и поэтому получает текущее значение этой переменной.

Это удивительно почти для всех, и приводит к большой путанице и множеству сообщений об ошибках. Мы рассматриваем изменение спецификации и реализации для гипотетической будущей версии C #, чтобы переменная цикла логически объявлялась внутри конструкции цикла, давая «свежую» переменную каждый раз через цикл.

Это было бы серьезным изменением , но я подозреваю, что число людей, которые зависят от этого странного поведения, довольно мало. Если у вас есть мнения по этому вопросу, не стесняйтесь добавлять комментарии к сообщениям в блоге, упомянутым выше в обновлении. Спасибо!

5 голосов
/ 21 октября 2009

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

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

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

1 голос
/ 21 октября 2009

Если ItemCollection представляет собой (универсальный) список , вы можете использовать его ForEach -метод. Это даст вам новые возможности для каждого:

ItemCollection.ForEach(
    i =>
    {
        Something s = new Something();
        s.EventX += delegate { ProcessItem(i); };
        SomethingCollection.Add(s);
    });

Или вы можете использовать любой другой подходящий Linq -метод - например, Выбрать :

var somethings = ItemCollection.Select(
        i =>
        {
            Something s = new Something();
            s.EventX += delegate { ProcessItem(i); };
            return s;
        });
foreach(Something s in somethings)
    SomethingCollection.Add(s);
0 голосов
/ 21 октября 2009
foreach(Item i on ItemCollection)
{
   Something s = new Something(i);
   s.EventX += (sender, eventArgs) => { ProcessItem(eventArgs.Item);};
   SomethingCollection.Add(s);
}

не могли бы вы просто передать 'i' в свой класс 'Something' и использовать его в аргументах событий EventX

0 голосов
/ 21 октября 2009

Проблема, с которой вы здесь столкнулись, связана с такой языковой конструкцией, как closure . Второй фрагмент кода устраняет проблему.

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