一、定義
委托類似於C++的函數指針,但是委托時類型安全的。一個不好聽的比喻,生前寫了一個遺囑,死后遺囑才會公開。委托的意義就是在某個特定的時間做某事,比如點擊一個按鈕會發生某事,但是你不想修改按鈕的代碼,可以添加一個方法到委托上,當點擊按鈕時,會自動執行委托鏈上的方法。委托增加了復雜性但是也增加了靈活性,方便程序的設計。
二、委托與事件
讓委托工作起來需要的步驟:
1. 聲明委托類型。
2. 創建委托實例。
3. 為委托實例添加匹配的方法。
4. 調用委托實例。
下面是一個簡單的委托例子。
版本一:
class Patent1
{
// 聲明委托類型
public delegate void PatentHandler();
// 創建委托實例
public PatentHandler patentHandler = null;
// 時間限制,到100
private int limit;
public Patent1(int limit)
{
this.limit = limit;
}
// 驅動函數
public void Grow(int limit)
{
this.limit += limit;
// 時間到100時,調用委托實例
if (this.limit >= 100 && this.patentHandler !=null)
{
patentHandler();
}
}
}
主函數調用:
static void Main()
{
Patent1 patent = new Patent1(40);
// 為委托實例添加方法,Expire方法是與委托類型匹配的方法
patent.patentHandler = new Patent1.PatentHandler(Expire);
// limit沒到100,不調用委托實例
patent.Grow(30);
// 調用委托實例
patent.Grow(30);
}
// 與PatentHandler委托匹配的方法
static void Expire()
{
Console.WriteLine("limit到了100,函數被觸發...");
}
從兩面的例子中可以看到4個步驟。上例子中委托實例的訪問修飾符是public,所以在其他類可以自由調用,於是問題出現了:
假如主函數調用改為
static void Main()patent.patentHandler = null;
{
Patent1 patent = new Patent1(40);
// 為委托實例添加方法,Expire方法是與委托類型匹配的方法patent.patentHandler.Invoke();
patent.patentHandler = new Patent1.PatentHandler(Expire);
// 直接調用委托實例
// limit沒到100,不調用委托實例
patent.Grow(30);
// 調用委托實例
patent.Grow(30);
}
問題一:
添加了一個語句patent.patentHandler.Invoke();,這樣直接就可以調用委托實例了,例子中的情景:專利的年限是100,當滿100的時候,才能公開,即調用委托實例。但是客戶端調用的時候卻可以繞過limit的限制,直接調用委托實例,這就導致了專利年限沒滿就公開了,大大的不好!
問題二:
添加了一個語句patent.patentHandler = null; 將委托實例的調用列表清空。
版本二:
為了避免上述的兩個問題,需要將委托實例的聲明為private。但是需要給委托實例注冊函數,但是委托實例是私有的,因此需要通過兩個函數來幫助委托實例注冊和取消注冊函數。好比一個私有字段,需要兩個函數getter和setter函數來輔助。於是有一個新版本,比第一個版本多了一下代碼
// 創建委托實例,聲明為private
private PatentHandler patentHandler;
// 為委托實例注冊函數
public void Add_Delegate(PatentHandler ph)
{
if (this.patentHandler == null)
{
this.patentHandler = ph;
}
else
{
this.patentHandler += ph;
}
}
// 為委托實例取消注冊函數
public void Remove_Delegate(PatentHandler ph)
{
if (this.patentHandler != null)
{
this.patentHandler -= ph;
}
}
主函數調用:
Patent2 patent = new Patent2(40);
patent.Add_Delegate(new Patent2.PatentHandler(Expire));
patent.Add_Delegate(new Patent2.PatentHandler(Expire1));
patent.Remove_Delegate(new Patent2.PatentHandler(Expire));
//patent.patentHandler.Invoke();
patent.Grow(60);
由於委托實例為私有,所以不存在版本一的兩種問題。為了解決這個問題每次都得自己寫兩個輔助函數,是在是太麻煩了。要是有個東西可以把兩個函數封裝起來就好了。於是語法糖 事件(event)出現了。
版本三:
class Patent3public event PatentHandler patentEvent;
{
// 聲明委托類型
public delegate void PatentHandler();
patentEvent();
private int limit;
public Patent3(int limit)
{
this.limit = limit;
}
// 驅動函數
public void Grow(int limit)
{
this.limit += limit;
if (this.limit >= 100 && this.patentEvent != null)
{
}
}
}
與第一個版本的區別用紅色標出。
主函數調用:
Patent3 patent = new Patent3(40);
patent.patentEvent += new Patent3.PatentHandler(Expire);
patent.Grow(60);
功能和版本二一樣,但是卻簡潔了很多。那事件到底是何方神聖。
聲明一個事件之后,編譯器會將事件轉換為具有默認實現的add_xxx/remove_xxx的方法和一個私有委托實例字段。私有委托實例字段對類內可見,成對的add/remove方法對類外可見,對應於代碼中的+=和-=。
因此:事件不是委托實例,而是成對的對私有委托實例字段進行操作的已實現的add/remove方法,類似於屬性的getter和setter方法,為的是封裝數據。
三、委托的歷史
c#1.0
c#2.0
在c#2.0中添加了 方法組的隱式轉換。
添加了匿名函數和委托的協變性/逆變性。
c#3.0
lambda表達式。