2013-12-22 46 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。

這意味着你會得到你想要的行爲。但在許多應用程序中,這是一個導致內存泄漏的問題。這就是編寫代碼以取消訂閱事件或使用弱事件模式非常重要的原因。