代码之家  ›  专栏  ›  技术社区  ›  FerranB Tom

关于foreach和代表的问题

  •  5
  • FerranB Tom  · 技术社区  · 15 年前

    假设以下代码:

    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 年前
        1
  •  11
  •   Eric Lippert    11 年前

    更新:对这个问题有广泛的分析和评论:

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


    这是一个非常经常报告的问题;通常报告为编译器错误,但实际上编译器根据规范做了正确的事情。匿名函数关闭 变量 不是 价值观 ,并且只有一个foreach循环变量。因此,每个lambda都会关闭同一个变量,从而获取该变量的当前值。

    这对几乎所有人来说都是令人惊讶的,并导致许多混乱和许多错误报告。我们是 考虑到 更改假设的C未来版本的规范和实现,以便循环变量在循环结构中逻辑声明,每次循环都会给出一个“新”变量。

    这将是一个突破性的变化 但是我怀疑依赖这种奇怪行为的人的数量非常少。如果你对这个主题有意见,可以在上面提到的更新中添加评论。谢谢!

        2
  •  5
  •   AnthonyWJones    15 年前

    第二块代码就是让所有其他东西保持不变的最佳方法。

    但是,可以在上创建属性 Something 采取什么 Item . 反过来,事件代码可以访问这个 项目 离开事件的发送者,或者它可能包含在事件的EventArgs中。因此无需关闭。

    我个人添加了“消除不必要的闭包”作为有价值的重构,因为很难对它们进行推理。

        3
  •  1
  •   Johan Kullbom    15 年前

    如果itemcollection是 (generic) List 您可以使用它的 ForEach -方法。它将为您提供每个i的新范围:

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

    或者你可以用其他合适的 Linq -类方法 Select :

    var somethings = ItemCollection.Select(
            i =>
            {
                Something s = new Something();
                s.EventX += delegate { ProcessItem(i); };
                return s;
            });
    foreach(Something s in somethings)
        SomethingCollection.Add(s);
    
        4
  •  0
  •   Vitaliy Liptchinsky    15 年前

    您在这里面临的问题与以下语言结构有关: 关闭 . 第二段代码解决了问题。

        5
  •  0
  •   Hath    15 年前
    foreach(Item i on ItemCollection)
    {
       Something s = new Something(i);
       s.EventX += (sender, eventArgs) => { ProcessItem(eventArgs.Item);};
       SomethingCollection.Add(s);
    }
    

    你能不能把“i”传入你的“something”类,并在eventx的event参数中使用它?