Code1:
1 private delegate void BloodNumDelegate (); 2 3 public delegate void ExpNumChangeDelegate (int _currentExpNum); 4 5 public event ExpNumChangeDelegate mOnExpNumChangeDelegate;
Code2:
PlayerVoProxy.instance.mOnExpNumChangeDelegate += delegate(int _expNum) { Console.WriteLine ("阿呆收到, ExpNum is : {0}", _expNum); };
如果你可以很清楚明白上面兩部分代碼是什麼意思,說明你已經很了解Delegate(至少比我了解:) ),
所以我想下面的文章也許對你的幫助不是很大. 不過如果某些地方你也有所疑惑,希望你能從我下面的文章中得到一些幫助.
這篇文章從最基本的Delegate開始介紹起,後續會引申到event, 匿名Delegate 及我個人對其使用的一點理解
提前需要閱讀的文章
如果你像我當初一樣對Delegate完全不理解,請先閱讀
NET之美:.NET關鍵技術深入解析.NET之美:.NET關鍵技術深入解析 迷你書 中的 第3章 (C# 中的委托和事件).
(作者對Delegate理解的及其深入,我是比不上了 :( )
從觀察者模式說起
觀察者模式大家肯定都不陌生
using System; using System.Collections; namespace L1 { public class FakeBloodChangeDelegate { public ArrayList mAllFakeDelegateListener; public FakeBloodChangeDelegate () { mAllFakeDelegateListener = new ArrayList (); } public void addListener (IFakeBloodNumChangeDelegateListener _listener) { mAllFakeDelegateListener.Add (_listener); } public void upldateListener () { foreach (IFakeBloodNumChangeDelegateListener listener in mAllFakeDelegateListener) { listener.onBloodNumChange (); } } } }
using System; namespace L1 { public interface IFakeBloodNumChangeDelegateListener { void onBloodNumChange (); } }
using System; namespace L1 { public class Idiot : IFakeBloodNumChangeDelegateListener { public Idiot () { } public void onBloodNumChange () { Console.WriteLine ("Idiot --> onBloodNumChange"); } } }
using System; namespace L1 { public class TestArea1 { private FakeBloodChangeDelegate mFakeBloodChangeDelegate; private Idiot mIdiot; public TestArea1 () { } public void run () { mFakeBloodChangeDelegate = new FakeBloodChangeDelegate (); mIdiot = new Idiot (); mFakeBloodChangeDelegate.addListener (mIdiot); mFakeBloodChangeDelegate.upldateListener (); } } }
這是一個比較標准的觀察者, idiot是監聽的人,當主體(FakeBloodChangeDelegate)發生變化時候調用 updateListener方法告訴所有監聽對象
Ok, 接下來讓我們稍微對這個FakeBloodChangeDelegate類進行一個簡單的封裝, 為其創建一個Player對象,Blood是Player的一部分
using System; namespace L1 { public class FakePlayerVoProxy { public FakeBloodChangeDelegate bloodNumChangeListner; private int mCurrentBloodNum = 0; public FakePlayerVoProxy () { bloodNumChangeListner = new FakeBloodChangeDelegate (); } public void addPlayerBlood (int _addNum) { mCurrentBloodNum += _addNum; bloodNumChangeListner.upldateListener (); } } }
於是,主類中的調用及變為:
using System; namespace L1 { public class TestArea1 { private FakePlayerVoProxy mPlayerVo; private Idiot mIdiot; public TestArea1 () { } public void run () { mPlayerVo = new FakePlayerVoProxy (); mIdiot = new Idiot (); mPlayerVo.bloodNumChangeListner.addListener (mIdiot); mPlayerVo.addPlayerBlood (10); } } }
運行一下代碼,會得到和上面一樣的輸出。 畢竟其實只是簡單的對FakeBloodChangeDelegate進行一個封裝,沒有改變任何的代碼。
如果上面的例子可以完全理解,就讓我們往下繼續
Delegate 就是一個類
這個其實是一個很重要的信息,在上面提到的那本迷你書裡面已經說的非常清楚了. 我之所以把代碼蹩腳的寫成這個模樣 其實也是為了說明這件事。
上面例子中的FakeBloodChangeDelegate,包括對應的Listener可以簡化為一行代碼
public delegate void BloodNumChangeDelegate ();
先拋開public不說, 後面用 delegate void BloodNumChangeDelegate(); 相當於就是告訴編輯器為你創建出 一個
FakeBloodChangeDelegate 和 IFakeBloodNumChangeDelegateListener (其實這塊是不准確的表達,不過目前可以先不要細糾結到時是怎麼回事,當看完後面的內容後 可以回頭再去看 上面我提到的那本迷你書,裡面講的很詳細)
using System; namespace L1 { public class FakePlayerVoProxy { public delegate void BloodNumChangeDelegate (); public BloodNumChangeDelegate bloodNumChangeListner; private int mCurrentBloodNum = 0; public FakePlayerVoProxy () { } public void addPlayerBlood (int _addNum) { mCurrentBloodNum += _addNum; bloodNumChangeListner (); } } }
注意
public delegate void BloodNumChangeDelegate (); 這行表示創建了FakeBloodChangeDelegate 類其實和 FackPlayerVoProxy這裡類沒有任何關系,
完全可以單獨建立一個文件,起名Apple,然後把這行放在那個Apple的類裡面 不過根據這個類(Delegate)創建對象時候你就不能寫
public BloodNumChangeDelegate bloodNumChangeListner;
而要寫
public Apple.BloodNumChangeDelegate bloodNumChangeListner;
個人關於Delegate的一點理解
因為是寫AS出身(ActionSprite),所以對於回調函數一點都不陌生,而Delegate就可以理解為C#中的回調函數,唯一會比較費解的會是
public delegate void BloodNumChangeDelegate (); public BloodNumChangeDelegate bloodNumChangeListner;
這兩行代碼,這塊的核心就是要理解 Delegate其實就是告訴編譯器為你生成一個實現了觀察者模式的類.
關於Delegate前面的Public修飾符
之前我在解釋Delegate是怎麼一回事時候,故意忽略掉了其前面的類修飾符 public. 這塊其實還是很有文章的. 對於Delegate或者回歸本質,對於觀察者模式,最主要的三個函數應該是
addListener, removeListener, updateListener
而OO的思想就是 一定要封裝 對於外部監聽者 只應該能訪問到 addListener, removeListener 而upldateListner 這個應該只有主類才可以做的事情.
讓我們先退回到實現Delegate之前的例子上面,繼續使用FakeBloodChangeDelegate ,
若要實現以上的需求,僅需要對應的將FakeBloodChangeDelegate改為private 並提供相應的public函數即可
using System; namespace L1 { public class FakePlayerVoProxy { private FakeBloodChangeDelegate mBloodNumChangeListner; private int mCurrentBloodNum = 0; public FakePlayerVoProxy () { mBloodNumChangeListner = new FakeBloodChangeDelegate (); } public void addListener(IFakeBloodNumChangeDelegateListener _listener) { mBloodNumChangeListner.Add(_listener); } public void removeListener(IFakeBloodNumChangeDelegateListener _listener){//對應Remove代碼} public void addPlayerBlood (int _addNum) { mCurrentBloodNum += _addNum; bloodNumChangeListner.upldateListener (); } } }
這塊應該很好理解吧? 所以呢如果使用Delegate去實現同樣的操作及可以寫成:
using System; namespace L1 { public class FakePlayerVoProxy { public delegate void BloodNumChangeDelegate (); private BloodNumChangeDelegate bloodNumChangeListner; private int mCurrentBloodNum = 0; public FakePlayerVoProxy () { } public void addListener (?? _listener) { bloodNumChangeListner += _listener; } public void addPlayerBlood (int _addNum) { mCurrentBloodNum += _addNum; bloodNumChangeListner (); } } }
你會發現 在addListener這裡 不知道該怎麼寫了(其實是可以寫的,不過如果這塊你都理解怎麼寫了也就不用再繼續往下讀啦 哈), 這個 _listener應該是什麼類型呢?
使用Event關鍵字
在推薦的那本迷你書裡面(什麼?! 還沒看...) 已經把這塊說的非常明白了, 為了封裝Delegate,對外僅提供add,remove 但是不提供
update方法, 但是如果不寫成public 又沒辦法對外訪問,所以及產生了 Event這個關鍵字
using System; namespace L1 { public class FakePlayerVoProxy { public delegate void BloodNumChangeDelegate (); public event BloodNumChangeDelegate bloodNumChangeListner; private int mCurrentBloodNum = 0; public FakePlayerVoProxy () { } public void addPlayerBlood (int _addNum) { mCurrentBloodNum += _addNum; bloodNumChangeListner (); } } }
using System; namespace L1 { public class TestArea1 { private FakePlayerVoProxy mPlayerVo; private Idiot mIdiot; public TestArea1 () { } public void run () { mPlayerVo = new FakePlayerVoProxy (); mIdiot = new Idiot (); mPlayerVo.bloodNumChangeListner += mIdiot.onBloodNumChange; mPlayerVo.addPlayerBlood (10); } } }
如果在TestArea1中調用 mPlayerVo.bloodNumChangeListner ();
會收到
Error(14,14): Error CS0070: The event `L1.FakePlayerVoProxy.bloodNumChangeListner' can only appear on the left hand side of += or -= when used outside of the type `L1.FakePlayerVoProxy' (CS0070) (L1)
也就是說在類的外部是無法調用"updateListener" 這個方法了. 這樣也就符合了OO得封裝原則
結束語
到此, Delegate和Event的用法 我想大家應該有了一個初步的認識,後續只要在項目中多試幾次 應該就能有很好的掌握吧
(其實我接觸C#也才不到一周,所以能理解的也就到這裡了,希望對大家有幫助.)
等等! Code2中的 代碼是什麼?
PlayerVoProxy.instance.mOnExpNumChangeDelegate += delegate(int _expNum) { Console.WriteLine ("阿呆收到, ExpNum is : {0}", _expNum); };
其實這個代碼 可以理解為匿名函數( 其實是匿名委托)
比如對於一個按鈕
private void fun1() { dummyBtn.onClick+=fun2 } private void fun2() { //Fun2Code }
但是如果fun2的代碼很簡單 或者其他原因 也可以使用 匿名委托 也就是如上的寫法
private void fun1() { dummyBtn.onClick+=delegate() { //Fun2Code } }
一般在按鈕的處理以及Tween操作時候大多願意這樣去做,不過我個人還是不太愛使用匿名委托(As3中是匿名函數)這種方式做處理. 因人而異吧.
真的不能使用 private BloodNumChangeDelegate bloodNumChangeListner 麼?
using System; namespace L1 { public class FakePlayerVoProxy { public delegate void BloodNumChangeDelegate (); private BloodNumChangeDelegate bloodNumChangeListner; private int mCurrentBloodNum = 0; public FakePlayerVoProxy () { } public void addListener(BloodNumChangeDelegate _listener) { bloodNumChangeListner += _listener; } public void addPlayerBlood (int _addNum) { mCurrentBloodNum += _addNum; bloodNumChangeListner (); } } }
using System; namespace L1 { public class TestArea1 { private FakePlayerVoProxy mPlayerVo; private Idiot mIdiot; public TestArea1 () { } public void run () { mPlayerVo = new FakePlayerVoProxy (); mIdiot = new Idiot (); mPlayerVo.addListener (mIdiot.onBloodNumChange); mPlayerVo.addPlayerBlood (10); } } }
以上兩段代碼是可以編譯過並且可以運行的, 這個是我突然看到匿名委托使用的是Delegate修飾符所想到的,不過我對這塊理解的也不是很透 如果有人知道原因 麻煩也給我解釋一下吧
相關的內容 也許和 溫故而知新:Delegate,Action,Func,匿名方法,匿名委托,事件 這篇文章提到的內容有關.
That's All
Eran.