在迭代集合时订阅闭包中的事件会导致订阅该事件的最后一项

Aer*_*ace 1 c# linq foreach

代码:

using System;
using System.Collections.Generic;

namespace so {

   public abstract class Feature {
      public void doIt() {
         Console.WriteLine( GetType().FullName );
      }
   }

   class A : Feature { }
   class B : Feature { }
   class C : Feature { }

   public class SSCCE {

      event EventHandler Click;

      static void Main( string[] args ) {
         SSCCE sscce = new SSCCE();
         List<Feature> features = new List<Feature>();
         features.Add( new A());
         features.Add( new B() );
         features.Add( new C() );
         foreach ( Feature feature in features ) {
            sscce.Click += ( object sender, EventArgs e ) => { feature.doIt(); };
         }
         sscce.Click.Invoke( null, null );
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

预期结果:

so.A
so.B
so.C
Run Code Online (Sandbox Code Playgroud)

观察结果:

so.C
so.C
so.C
Run Code Online (Sandbox Code Playgroud)

在Java中,final在前面的关键字Featureforeach循环允许feature在拉姆达的动作的主体之前使用的值,.doIt().

C#中的语法有什么用?

Ser*_*kiy 5

最后一个功能被lambda捕获(它是一个闭包).您应该创建局部变量以捕获每次迭代的功能:

 foreach (Feature feature in features) {
    Feature current = feature;
    sscce.Click += (object sender, EventArgs e) => { current.doIt(); };
 }
Run Code Online (Sandbox Code Playgroud)

我建议你阅读Eric Lippert博客上关闭循环变量文章.

注意:在最后一个版本的C#中修复了这个问题


要了解会发生什么,让我们来看看您的案例中生成的代码(在C#5之前).因此你的lambda使用局部变量,它不足以生成方法 - 编译器生成私有类,它捕获lambda中使用的局部变量:

private sealed class AnonymousClass
{
    public Feature feature;

    public void AnonymousMethod(object sender, EventArgs e)
    {
        this.feature.doIt();
    }
}
Run Code Online (Sandbox Code Playgroud)

并修改了您的代码,以便它使用此AnonymousClass的实例并将其AnonymousMethod订阅到Click事件:

using(var enumerator = ((IEnumerable<Feature>)features).GetEnumerator())
{ 
  AnonymousClass x = new AnonymousClass();

  while(enumerator.MoveNext())
  {
     x.feature = (Feature)enumerator.Current;
     sscce.Click += new EventHandler(x.AnonymousMethod);
  }
}
Run Code Online (Sandbox Code Playgroud)

如您所见,您已多次订阅同一AnonymousClass实例的AnonymousMethod.并且该实例将具有与最后指定的特征相同的特征.现在,将当前要素复制到局部变量时会发生什么变化:

using(var enumerator = ((IEnumerable<Feature>)features).GetEnumerator())
{
  while(enumerator.MoveNext())
  {
     AnonymousClass x = new AnonymousClass();
     x.current = (Feature)enumerator.Current; // field has local variable name
     sscce.Click += new EventHandler(x.AnonymousMethod);
  }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,AnonymousClass实例在每次迭代时创建,因此不同类实例的AnonymousMethods(每个都有捕获自己的特性)将处理Click事件.为什么代码不同 - 因为,正如Eric所说,闭包(即匿名类)是关闭变量的.为了在循环体中关闭局部变量,在第二种情况下,应在循环内创建匿名类的实例.