1. 程式人生 > >C#委托與事件學習筆記

C#委托與事件學習筆記

調用函數 msdn http msd proc for reac .net 委托方

今天跟隨視頻學習了一下C#中最重要的一些概念之委托與事件。老楊的視頻講的還是挺深入淺出,不過剛接觸C#.NET的人還是朦朦朧朧,就像張子陽先生說的“每次見到委托和事件就覺得心裏別(biè)得慌,混身不自在”。跨過這道坎的人就有種一覽眾山小的感覺了。我又瀏覽了皺華棟老師JamesZou的博文《深入理解C#委托及原理》(地址:http://www.cnblogs.com/jameszou/archive/2011/07/21/2112497.html),以及張子陽Jimmy Zhang的博文《C# 中的委托和事件》(地址:http://www.cnblogs.com/jimmyzhang/archive/2007/09/23/903360.html)總算對委托有了一點理性的感覺了,在此謝謝ITCAST,JamesZou以及Jimmmy Zhang的博文,謝謝。

1.委托是神馬?

  用最通俗易懂的話來講,你就可以把委托看成是用來執行方法(函數)的一個“指針”。用鄒老師的一個舉例:“設想,如果我們寫了一個廚師做菜方法用來做菜,裏面有拿菜、切菜、配菜、炒菜 四個環節,但編寫此方法代碼的人想讓配菜這個環節讓調用方法的人實現,換句話說,就是想在方法被調用時接收代碼 作為參數,在方法中執行這端傳進來的代碼。但,怎麽為一個方法傳 代碼 進來呢?當然大家想到了傳遞接口方式來實現,咱先不討論接口,因為微軟為我們提供了一個叫做委托的類型。”

  現在來看看怎樣使用委托,根據itcast的ppt內容:

  聲明委托的方式:delegate 返回值類型 委托類型名(參數) 比如delegate void StringProcess(string s); 註意這裏的除了前面的delegate,剩下部分和聲明一個函數一樣,但是StringProcess不是函數名,而是委托類型名
  聲明的委托是一種類型,就像int、Person一樣,如果要用的話還要聲明委托類型的變量,聲明委托類型變量的方式:StringProcess f1;
  將委托類型變量指向函數 StringProcess sp = new StringProcess(SayHello),這樣就可以像調用普通函數一樣把sp當成函數用了。委托可以看做是函數的指針。整數可以用整數變量指向它,對象可以用對象變量指向它,函數也可以用委托變量指向它。和直接調用函數的區別:用委托就可以指向任意的函數,哪怕是之前沒定義的都可以,而不使用受限於那幾種。
  將委托類型變量指向函數還可以簡化成StringProcess sp = SayHello,編譯器幫我們進行了new。但是不能sp=PrintIt(),因為這樣就成了“執行PrintIt函數,並且將sp指向PrintIt的返回值”。

  這裏看一個數據過濾的例子,輸出int數組中的正整數:

  1.聲明一個委托:delegate bool FilterDelegate(int i);

  2.封裝一個過濾的靜態方法,參數中包含一個過濾器的方法委托,返回泛型List<int>列表:

  static List<int> Filter(List<int> list,FilterDelegate fd)
{
List<int> listTest = new List<int>();
foreach(int i in list)
{
if(fd(i))
{
listTest.Add(i);
}
}
return listTest;
}

  3.寫一個判斷是否為正整數的方法,返回值為bool類型:

   static bool isZhengshu(int i)
{
return i > 0;
}

  4.在main函數中聲明一個List列表,然後添加部分測試數據,將委托指向判斷正整數的方法,最後遍歷輸出過濾後的數組數據;

  List<int> listOne = new List<int>();
listOne.Add(1);
listOne.Add(-4);
listOne.Add(8);
listOne.Add(-6);
listOne.Add(13);

FilterDelegate fd = isZhengshu;
List<int> listResult = Filter(listOne, isZhengshu);
foreach (int i in listResult)
{
Console.WriteLine(i);
} 

  運行後,顯示:1 8 13 

  通過一個小例子,可以得出一個小結論:C# 中的委托類似於 C 或 C++ 中的函數指針。使用委托使程序員可以將方法引用封裝在委托對象內。然後調用該委托對象就可以執行委托對象內方法引用指向的方法,而不必在編譯時知道將調用哪個方法(如參數為委托類型的方法,也就是提供了為程序回調指定方法的機制)。”摘錄自MSDN;

  鄒老師的通俗說法是:“就是一個能存放很多方法的指針的調用清單(但方法簽名必須和委托類型簽名一樣),你一調用這個清單,那麽清單裏的所有的指針所對應的方法就會依次被執行。”

  而委托的原理是神馬?這裏就需要跟隨鄒老師的博文走走了,通過VS中自帶的MSIL反編譯程序,將生成後的.exe拖到工具中查看委托類型聲明的代碼,發現其編譯前就生成了一個類;它繼承了System.MulticastDelegate,包含了構造方法、BeginInvoke、EndInvoke、Invoke方法。另外MulticastDelegate則繼承自Delegate類。通過Reflector反編譯工具,可以看出:繼承關系:編譯前生成的類 –> MulticastDelegate–> Delegate,而MulticastDelegate類中有3個重要的成員,其中兩個繼承自 Delegate:

技術分享

  

  

  這三者的作用分別是:

  _methodPtr 裏保存的就是 方法指針。

  _target 裏用來保存方法所在的對象。

  _invocationList 其實使用時是個object數組,在註冊多個方法時,其他方法就保存在此成員中,而它也就是 委托鏈 的關鍵容器。--摘自鄒老師的博文;

2.事件閃亮出場 

  下面來看一個通過委托實現打招呼Greeting的例子(感謝張子陽先生的博文,此例選自其博文)

  1.兩種不同的Greeting方式:

   static void ChineseGreeting(string name)
{
Console.WriteLine("早上好,"+name);
}

static void EnglishGreeting(string name)
{
Console.WriteLine("Morning,"+name);
}

  2.聲明一個委托:

  public delegate void GreetingDelegate(string name);

  3.封裝一個類,其中包含一個事件:

  public class GreetingManager
{
public GreetingDelegate onGreeting;

public void ProcessGreeting(string name)
{
if (onGreeting != null)
{
onGreeting(name);
}
}
}

  4.客戶端調用:

  GreetingManager gm = new GreetingManager();
gm.onGreeting += EnglishGreeting;
gm.onGreeting += ChineseGreeting;
gm.ProcessGreeting("Edison Chou");

  5.運行顯示結果:

  Hello,Edison Chou

  你好,Edison Chou

  這裏有一個問題,如果將委托聲明為private權限,那麽:“這簡直就是在搞笑。因為聲明委托的目的就是為了把它暴露在類的客戶端進行方法的註冊,你把它聲明為private了,客戶端對它根本就不可見,那它還有什麽用?”-(摘自張子陽的原話)

  那麽如果就將委托設置為public,則客戶端可以清空監聽(即設置為null,因為它是引用類型),也可以偽造監聽(即直接調用委托),破壞了其封裝性。最後,第一個方法註冊用“=”,是賦值語法,因為要進行實例化,第二個方法註冊則用的是“+=”。但是,不管是賦值還是註冊,都是將方法綁定到委托上,除了調用時先後順序不同,再沒有任何的分別,這樣不是讓人覺得很別扭麽?

  該怎麽解決呢?於是Event事件閃亮登場了!!!它封裝了委托類型的變量,使得:在類的內部,不管你聲明它是public還是protected,它總是private的。在類的外部,註冊“+=”和註銷“-=”的訪問限定符與你在聲明事件時使用的訪問符相同。

  於是,我改寫GreetingManager類:

  

  public class GreetingManager
{
public event GreetingDelegate onGreeting;

public void ProcessGreeting(string name)
{
if (onGreeting != null)
{
onGreeting(name);
}
}
}

 這裏僅僅是加了一個event標誌,沒有神馬大的改變。但通過Reflector反編譯,可以看出事件其實就是一個封裝了的私有的委托而已,還包含兩個方法:add和remove;這兩個方法分別用於註冊委托類型的方法和取消註冊。實際上也就是: “+= ”對應 add_ProcessGreeting,“-=”對應remove_ProcessGreeting。而這兩個方法的訪問限制取決於聲明事件時的訪問限制符。所以,這下客戶端只能註冊、註銷事件,無法進行偽造和清空事件,保證了封裝性。

3.委托和事件的區別

  委托和事件沒有可比性,因為委托是類型,事件是對象。而委托的對象(用委托方式實現的事件)與標準event方式實現的事件的區別是:事件的內部是用委托實現的。

C#委托與事件學習筆記