2015-11-24 38 views
0

我有3個類,形成一棵樹。我正在使用System.Actions來傳達事件。但我很困惑爲什麼訂購我訂購事項。檢查這個僞代碼:C#將一個函數訂閱到另一個System.Action的System.Action中:爲什麼訂單很重要?

class Leaf 
{ 
    public System.Action OnTrigger; 
    public void Go() 
    { 
     if (OnTrigger != null) 
      OnTrigger(); 
    } 
} 

class Chunk 
{ 
    public System.Action OnTrigger; 
    public Leaf leaf = null; 
    public Chunk() 
    { 
     leaf = new Leaf(); 
    } 
} 

class Tree 
{ 
    void Hello() 
    { 
     Debug.Log("Hello"); 
    } 
    void World() 
    { 
     Debug.Log("World"); 
    } 

    public Tree() 
    { 
     var chunk2 = new Chunk(); 
     chunk2.OnTrigger += Hello; // OK: will be called 
     chunk2.leaf.OnTrigger += chunk2.OnTrigger; 
     chunk2.OnTrigger += World; // NOT: not be called 
     chunk2.leaf.Go(); 
    } 
} 

最好的問候,

馬薩

+1

正如一個側面說明你給你的事件的名稱是非標準的。引發一個事件'OnTrigger'的事件是正常的,但事件本身通常被簡稱爲'Trigger'。所以,如果你的'Leaf'類的方法'Go'應該是'OnTrigger',並且事件應該是'Trigger',以使它們更像標準。 – Enigmativity

回答

1

通過檢查委託人的工作方式以及編譯時創建的MSIL,這一點相對明顯。首先讓我們注意到System.Action是一個System.Delegate,它有ICloneable接口。

當你編譯這個程序,在這裏幾乎是你會得到什麼:

 
    IL_0015: newobj  instance void [mscorlib]System.Action::.ctor(object, 
                    native int) 
    IL_001a: call  class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, 
                          class [mscorlib]System.Delegate) 
    IL_001f: castclass [mscorlib]System.Action 
    IL_0024: stfld  class [mscorlib]System.Action ConsoleTests.Program6/Chunk::OnTrigger 
    IL_0029: ldloc.0 
    IL_002a: ldfld  class ConsoleTests.Program6/Leaf ConsoleTests.Program6/Chunk::leaf 
    IL_002f: dup 
    IL_0030: ldfld  class [mscorlib]System.Action ConsoleTests.Program6/Leaf::OnTrigger 
    IL_0035: ldloc.0 
    IL_0036: ldfld  class [mscorlib]System.Action ConsoleTests.Program6/Chunk::OnTrigger 
    IL_003b: call  class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, 
                          class [mscorlib]System.Delegate) 
    IL_0040: castclass [mscorlib]System.Action 
    IL_0045: stfld  class [mscorlib]System.Action ConsoleTests.Program6/Leaf::OnTrigger 
    IL_004a: ldloc.0 
    IL_004b: dup 
    IL_004c: ldfld  class [mscorlib]System.Action ConsoleTests.Program6/Chunk::OnTrigger 
    IL_0051: ldnull 
    IL_0052: ldftn  void ConsoleTests.Program6::World() 
    IL_0058: newobj  instance void [mscorlib]System.Action::.ctor(object, 
                    native int) 
    IL_005d: call  class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, 
                          class [mscorlib]System.Delegate) 
    IL_0062: castclass [mscorlib]System.Action 
    IL_0067: stfld  class [mscorlib]System.Action ConsoleTests.Program6/Chunk::OnTrigger 
    IL_006c: ldloc.0 
    IL_006d: ldfld  class ConsoleTests.Program6/Leaf ConsoleTests.Program6/Chunk::leaf 
    IL_0072: callvirt instance void ConsoleTests.Program6/Leaf::Go() 

注意,當你在一個委託做一個+=,編譯器轉換是爲Combine呼叫,它結合了當前委託與通過在一個。現在看看IL_004b,這是一個dup調用。

從MSDN:「的評價堆棧上覆制當前最上面的值,然後按下複製到計算堆棧」。

這通知運行時複製堆棧上的對象,因爲這是ICloneable,它創建的對象的副本。現在,當您向其中添加其他代表時,您並未將它添加到兩個代理中,只是您正在操作的代理中的一個,因此保留原來的Leaf觸發器。

幾乎等效代碼是這樣的:

public Tree() 
{ 
    var chunk2 = new Chunk(); 
    chunk2.OnTrigger += Hello; // OK: will be called 
    chunk2.leaf.OnTrigger = (Action)chunk2.OnTrigger.Clone(); 
    chunk2.OnTrigger += World; // NOT: not be called 
    chunk2.leaf.Go(); 
} 

這可能使更清晰一點關於什麼是怎麼回事。

+0

感謝羅恩。我試圖實現級聯事件。我不知道它會克隆和合並原始列表。我認爲它會指向一個代表,然後將它稱爲它的功能... –

+0

我不得不實施一箇中介函數'void _OnTrigger(){if(OnTrigger!= null)OnTrigger();}'爲了創建這個級聯效應。醜陋和超級乏味。 –

1

這有什麼好做的秩序本身。

它與你所做的任務有關。

chunk2.OnTrigger += Hello; // this is **never** called 
    chunk2.leaf.OnTrigger += chunk2.OnTrigger; // this is the only one called 
    chunk2.OnTrigger += World; // this is **never** called 

當你調用chunk2.leaf.Go();觸發該事件僅是chunk2.leaf.OnTriggerchunk2.OnTrigger永遠不會被調用。

+0

我認爲混淆的是,OP認爲因爲'Action'是一個引用類型,所以一旦你設置了引用,對原文的任何操作都會影響到它們,我想我已經清除了爲什麼我的情況不是這樣回答。 –

相關問題