2013-12-22 48 views
4

让我们考察以下情形(转换事件):由拉姆达保持对象活着

public void HookSpecificButton(SpecificButton specificButton, EventHandler eh) 
{ 
    specificButton.SpecificClick += (o, e) => eh(o, EventArgs.Empty); 
} 

代码的全部意义在于事件从一种类型转换为另:我不关心传递的数据由specificButton通过SpecificClick,我想附加到这个事件定期EventHandler。

我的问题是以下。 eh包含对某个对象的方法的引用。如果没有其他引用该对象,lambda是否足以让该对象保持活动?链是:

specificButton保持活着的EventHandler<SpecificData>一个实例,这使活(lambda)其保持活着EventHandler一个实例,这使活的最终对象(?)。

+1

是的,就够了。 GC不会收集仍在使用的物体 – knittl

+0

在此情况下, “呃”包含对某个对象方法的引用 - 你能详细说明一下吗?它捕获函数调用eh。 –

+0

'eh'是一个委托 - 方法引用的容器。但是每个方法都必须在某个上下文中调用,所以委托包含对方法*和*的引用,这些方法是该方法的所有者。 – Spook

回答

2

对象保持活着。它仍然是“扎根”的,因为从按钮到包含由eh所指的方法的对象引用了一系列对象。

根据Simon Whitehead对您的问题的评论,编译器翻译此代码很有趣。借此扩大你的代码:

public partial class MainWindow : Window 
{ 
    public MainWindow() 
    { 
     this.InitializeComponent(); 
     HookSpecificButton(this.MyButton, this.OnButtonClicked); 
    } 

    private void OnButtonClicked(object sender, EventArgs e) 
    { 
    } 

    public static void HookSpecificButton(Button specificButton, EventHandler eh) 
    { 
     specificButton.Click += (o, e) => eh(o, EventArgs.Empty); 
    } 
} 

其中Click事件处理程序被钩线实际上是简写:

 specificButton.Click += new RoutedEventHandler((o, e) => eh(o, EventArgs.Empty)); 

这阐明你的确创造了RoutedEventHandler委托对象。委托(用于非静态方法调用)包装对目标对象的引用以及对该对象的实例方法的引用。

我们可以使用ILDasm检查lambda表达式会发生什么。我在MainWindow内看到一个嵌套的类,名为<>c__DisplayClass1。这个类别有一个类型的字段eh,以及一个需要objectRoutedEventArgs的方法。

因此,我们有以下参考资料:

  • ButtonmyButton的 - >RoutedEventHandler
  • RoutedEventHandler - ><>c__DisplayClass1
  • <>c__DisplayClass1 - >EventHandler
  • EventHandler - >MyWindowOnButtonClicked

下面是嵌套子类的MainWindow的反汇编输出:

.class auto ansi sealed nested private beforefieldinit '<>c__DisplayClass1' 
     extends [mscorlib]System.Object 
    { 
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (01 00 00 00) 
    .field public class [mscorlib]System.EventHandler eh 
    .method public hidebysig specialname rtspecialname 
      instance void .ctor() cil managed 
    { 
     // Code size  7 (0x7) 
     .maxstack 8 
     IL_0000: ldarg.0 
     IL_0001: call  instance void [mscorlib]System.Object::.ctor() 
     IL_0006: ret 
    } // end of method '<>c__DisplayClass1'::.ctor 

    .method public hidebysig instance void 
      '<HookSpecificButton>b__0'(object o, 
             class [PresentationCore]System.Windows.RoutedEventArgs e) cil managed 
    { 
     // Code size  18 (0x12) 
     .maxstack 8 
     IL_0000: ldarg.0 
     IL_0001: ldfld  class [mscorlib]System.EventHandler ObjectLifetimeTest.MainWindow/'<>c__DisplayClass1'::eh 
     IL_0006: ldarg.1 
     IL_0007: ldsfld  class [mscorlib]System.EventArgs [mscorlib]System.EventArgs::Empty 
     IL_000c: callvirt instance void [mscorlib]System.EventHandler::Invoke(object, 
                       class [mscorlib]System.EventArgs) 
     IL_0011: ret 
    } // end of method '<>c__DisplayClass1'::'<HookSpecificButton>b__0' 

    } // end of class '<>c__DisplayClass1' 

当然,在我的例子中所提供的事件处理程序反正扎根,因为它是在Window本身。但即使情况并非如此,它也不会被GC'd。

这意味着你会得到你想要的行为。但在许多应用程序中,这是一个导致内存泄漏的问题。这就是编写代码以取消订阅事件或使用弱事件模式非常重要的原因。