【问题标题】:Keeping object alive by a lambda通过 lambda 保持对象存活
【发布时间】:2014-01-11 02:23:22
【问题描述】:

让我们检查以下场景(翻译事件):

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

代码的重点是将事件从一种类型转换为另一种类型:我不关心 specificButtonSpecificClick 传递的数据,我想附加到这个事件常规 EventHandler。

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

specificButton 使EventHandler<SpecificData> 的实例保持活动状态,使(lambda) 保持活动状态 (?) EventHandler 的实例,使最终对象保持活动状态。

【问题讨论】:

  • 是的,够了。 GC 永远不会收集仍在使用的对象
  • 在这种情况下; “eh”包含对某个对象的方法的引用——你能详细说明一下吗?它捕获函数调用 eh。
  • eh 是一个委托 - 方法引用的容器。但是每个方法都必须在某些上下文中调用,因此委托包含对方法的引用对某个实例的引用,该实例是该方法的所有者。
  • 那个 lambda 在编译时被提升为一个类。因此,实际的根对象是该类的一个实例。
  • 你能告诉我们你为什么关心吗?通常 GC 做什么是无关紧要的。

标签: c# memory-management lambda garbage-collection


【解决方案1】:

对象保持活跃。它仍然是“根”的,因为从按钮到包含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 的类型为 EventHandler 的字段,以及一个采用 objectRoutedEventArgs 的方法。

所以我们有以下参考:

  • Button 我的按钮 -> RoutedEventHandler
  • RoutedEventHandler -> <>c__DisplayClass1
  • <>c__DisplayClass1 -> EventHandler
  • EventHandler eh -> MyWindow (OnButtonClicked)

这是 MainWindow 的嵌套子类的 ILDasm 输出:

  .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 处理。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-19
    • 2015-03-09
    相关资源
    最近更新 更多