2010-09-19 51 views
3

下面的代碼直接從MSDN introducing MVVM design pattern上的文章隨附的樣本項目中獲取。我不太明白爲什麼委託人會看到除「空」以外的「處理程序」的值。我的理解是,爲委託方法創建的閉包包含了在執行過程中已經初始化的所有變量,並且由於'處理程序'在委託創建後被重新分配,閉包將包含'處理程序'設置爲空。匿名代理關閉(或爲什麼這個工作)?

康斯坦丁


EventHandler handler = null; 
handler = delegate 
{ 
    viewModel.RequestClose -= handler; 
    window.Close(); 
}; 
viewModel.RequestClose += handler; 

回答

4

委託捕獲可變handler,而不是變量的內容。


當您查看C#編譯器將代碼編譯到的內容(例如使用Reflector)時,它會變得更加清晰。您的代碼大致編譯爲:

class MyAnonymousDelegate 
{ 
    public ... viewModel; 
    public ... window; 
    public EventHandler handler; 

    public void DoIt(object sender, EventArgs e) 
    { 
     this.viewModel.RequestClose -= this.handler; 
     this.window.Close(); 
    } 
} 

var mad = new MyAnonymousDelegate(); 
mad.viewModel = viewModel; 
mad.window = window; 
mad.handler = null; 

mad.handler = new EventHandler(mad.DoIt); 

viewModel.RequestClose += mad.handler; 
+0

@dtb:是的,您的示例類定義清楚地表明瞭這一點。假設它確實是原始代碼大致映射的內容。正如我在對另一個響應的評論中所說的那樣,這與其他語言中的閉包的語義不同,例如,在函數式語言中,我可以通過它所看到的所有環境獲得函數變量它的創建並不需要擔心有人會從這個環境中改變這個環境。或者我完全沒有? – akonsu 2010-09-19 19:39:24

+0

@akonsu:在函數式語言中,變量通常根本不可變,即同一變量在不同的時間點不能有不同的值。所以捕獲一個變量和一個函數式語言中變量的值沒有本質的區別,因爲無論如何變量總是具有相同的值。在C#中,變量*的值可以隨時間變化,因此語言設計者決定捕獲變量而不是值。在某些情況下,這有點令人驚訝,但允許更大的靈活性。 – dtb 2010-09-19 19:43:35

+0

好吧,我明白了,但如果它捕獲的只是變量而不是它們的值爲什麼csc會在發現EventHandler handler = delegate {...}時發生抱怨? :) – akonsu 2010-09-19 19:47:01

1

閉包不會複製變量,它會保留一個引用。在創建委託並設置處理程序時,會更新此更改。

+0

謝謝,我不知道這個......這與ML不同。對我來說,它似乎是一個偏離封閉的定義... – akonsu 2010-09-19 19:32:03

+0

不是。閉包持有並保留自己的變量,而不僅僅是價值觀。 – Puppy 2010-09-19 20:13:36

1

的處理程序asigning前初始化:

 static void Main(string[] args) 
    { 

     EventHandler handler = null; 

     handler = delegate 
     { 
      AppDomain.CurrentDomain.ProcessExit -= handler; 

     }; 
     AppDomain.CurrentDomain.ProcessExit += handler; 
    } 

編譯爲:

 
.method private hidebysig static void Main(string[] args) cil managed 
{ 
    .entrypoint 
    // Code size  51 (0x33) 
    .maxstack 4 
    .locals init ([0] class ConsoleApplication1.Program/'c__DisplayClass1' 'CS$8__locals2') 
    IL_0000: newobj  instance void ConsoleApplication1.Program/'c__DisplayClass1'::.ctor() 
    IL_0005: stloc.0 
    IL_0006: nop 
    IL_0007: ldloc.0 
    IL_0008: ldnull 
    IL_0009: stfld  class [mscorlib]System.EventHandler ConsoleApplication1.Program/'c__DisplayClass1'::'handler' 
    IL_000e: ldloc.0 
    IL_000f: ldloc.0 
    IL_0010: ldftn  instance void ConsoleApplication1.Program/'c__DisplayClass1'::'b__0'(object, 
                            class [mscorlib]System.EventArgs) 
    IL_0016: newobj  instance void [mscorlib]System.EventHandler::.ctor(object, 
                      native int) 
    IL_001b: stfld  class [mscorlib]System.EventHandler ConsoleApplication1.Program/'c__DisplayClass1'::'handler' 
    IL_0020: call  class [mscorlib]System.AppDomain [mscorlib]System.AppDomain::get_CurrentDomain() 
    IL_0025: ldloc.0 
    IL_0026: ldfld  class [mscorlib]System.EventHandler ConsoleApplication1.Program/'c__DisplayClass1'::'handler' 
    IL_002b: callvirt instance void [mscorlib]System.AppDomain::add_ProcessExit(class [mscorlib]System.EventHandler) 
    IL_0030: nop 
    IL_0031: nop 
    IL_0032: ret 
} // end of method Program::Main 
2

寫這樣的:

EventHandler handler = delegate 
{ 
    viewModel.RequestClose -= handler; 
    window.Close(); 
}; 
viewModel.RequestClose += handler; 

要獲取錯誤CS0165:使用未分配的局部變量「處理程序」。

可疑診斷,它實際上並未未分配。實際上實現匿名方法的隱藏類實際上是在分配'handler'的捕獲值之前創建的。儘管這在C#編譯器中實現並不容易。邊緣情況。