委托那些事兒


一、定義

委托類似於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()
{
Patent1 patent = new Patent1(40);

patent.patentHandler = null;
    // 為委托實例添加方法,Expire方法是與委托類型匹配的方法
patent.patentHandler = new Patent1.PatentHandler(Expire);

// 直接調用委托實例
patent.patentHandler.Invoke();


// 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 Patent3
{
// 聲明委托類型
public delegate void PatentHandler();

public event 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)
{
patentEvent();

}
}
}

與第一個版本的區別用紅色標出。

主函數調用:

Patent3 patent = new Patent3(40);
patent.patentEvent += new Patent3.PatentHandler(Expire);
patent.Grow(60);

功能和版本二一樣,但是卻簡潔了很多。那事件到底是何方神聖。

QQ截圖20120614235620

聲明一個事件之后,編譯器會將事件轉換為具有默認實現的add_xxx/remove_xxx的方法和一個私有委托實例字段。私有委托實例字段對類內可見,成對的add/remove方法對類外可見,對應於代碼中的+=和-=。

因此:事件不是委托實例,而是成對的對私有委托實例字段進行操作的已實現的add/remove方法,類似於屬性的getter和setter方法,為的是封裝數據。

三、委托的歷史

c#1.0

QQ截圖20120615001405

 
c#2.0

QQ截圖20120615001422

在c#2.0中添加了 方法組的隱式轉換。

QQ截圖20120615001500

添加了匿名函數和委托的協變性/逆變性。

c#3.0

QQ截圖20120615002159

lambda表達式。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM