.NET中的委托和事件(續(xù))

首先感謝大家對我博文的支持,在這篇博文中我會向大家繼續(xù)介紹.NET中的委托與事件,這次是對前面的知識的進(jìn)一步加深學(xué)習(xí)。首先回答一下前面各位提及到的問題。
-
委托事件返回類型一定要為void嗎?
答:多數(shù)情況下委托封裝方法都是返回void的,事實(shí)上多重委托最常見是在事件中,而.NET中對事件約定返回值為void并且二個參數(shù)分別是object和EventArgs類型的。但我們也可以實(shí)現(xiàn)返回值不為void委托方法,好然我們實(shí)現(xiàn)一個有返回值的委托。
/// <summary>
/// Define delegate and return int.
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public delegate int CalcNumber(int index);
public class PeopleCalculator
{
public static int CalcPeople1(int index)
{
return ++index;
}
public static int CalcPeople2(int index)
{
return index += 2;
}
}
現(xiàn)在我們定義了一個返回值為int的委托,然后我們再初始化委托變量就OK了。下面代碼實(shí)現(xiàn)委托變量初始化。
CalcNumber calcNumber;
calcNumber = new CalcNumber(PeopleCalculator.CalcPeople1);
calcNumber += new CalcNumber(PeopleCalculator.CalcPeople2);

圖1輸出結(jié)果
結(jié)果可以發(fā)現(xiàn)并沒有顯示2只有3,通過分析委托多播情況我們可以發(fā)現(xiàn),委托根據(jù)我們初始化委托變量次序去調(diào)用具體的實(shí)現(xiàn),那就說明其實(shí)方法CalcPeople1和CalcPeople2都有執(zhí)行,只是最后方法把前面的結(jié)果都覆蓋了。
現(xiàn)在我們要獲取調(diào)用的結(jié)果,所以我們必須獲取委托列表,并且顯式調(diào)用每個封裝的方法,通過.NET中提供DelegateName.GetInvocationList方法獲取委托列表代碼實(shí)現(xiàn)如下。
foreach (CalcNumber cn in calcNumber.GetInvocationList())
{
Console.WriteLine("Answer is {0}.\n", cn(1));
}
圖2多播委托返回值
-
怎樣實(shí)現(xiàn)給預(yù)定義委托傳遞繼承于EventArgs參數(shù)
如果使用.NET控件,使其傳遞自定義事件關(guān)聯(lián)類型,我覺得這樣是不可能的因?yàn)镋ventArgs本身是不帶任何信息,我們只能通過間接發(fā)送實(shí)現(xiàn)自定義事件關(guān)聯(lián)類型傳遞,下面我將細(xì)細(xì)解釋。
1.1.1匿名方法
匿名方法就是以內(nèi)聯(lián)方式實(shí)現(xiàn)委托,接下來我們看看如何定義匿名方法
delegate (parameters) {Implementation Code}
定義匿名方法如上很簡單,但細(xì)心的你肯定會發(fā)現(xiàn)怎么沒有指定返回類型,其實(shí)匿名方法省去:返回類型和函數(shù)名的定義,它會根據(jù)我們定義的委托的返回類型來判斷匿名方法的返回類型。
////Define delegate type.
private delegate int SayNumber(int number);
現(xiàn)在我們定義一個返回類型int的委托類型,和它匹配的匿名方法必需具有相同返回類型、相同參數(shù)個數(shù)及類型。
讓我們看一下如何定義一個正確的匿名函數(shù),首先我們定義委托類型,然后再定義兩個匿名方法如下:
/// <summary>
/// Define delegate type.
/// </summary>
/// <param name="number"></param>
private delegate void NumberProcessor(int number);
//// Anonymous method has parameter.
numberProcessor = delegate(int number)
{
Console.WriteLine("Hello I am number {0}.\n", ++number);
};
//// Anonymous method don't have any parameters.
numberProcessor += delegate
{
Console.WriteLine("I am fairly lazy, so I do nothing.\n");
};
大家猜猜看哪個和我們定義委托類型是匹配的,要注意的是這里的委托類型返回為void,激動人心的時刻又到了是時候公布答案。
?
?
?
?
圖3輸出結(jié)果
?
?
OK這次兩個匿名方法都能和我們的委托類型匹配上,這里要注意一點(diǎn)是當(dāng)我們的委托類型的返回值類型為void時,匿名函數(shù)匹配條件如下:
n 委托類型返回類型為void
n 匿名函數(shù)的具體實(shí)現(xiàn)沒有使用到委托傳遞參數(shù)
當(dāng)滿足以上條件時候,委托類型的匿名方法可無參數(shù)
接下來讓我們通過具體的代碼看一下顯式方法和匿名方法調(diào)用委托的區(qū)別。
?
/// <summary>
/// 使用顯示方法
/// </summary>
class Program
{
////Define delegate type.
private delegate int SayNumber(int number);
//Concrete invoke method.
private static int Say(int number)
{
return ++number;
}
static void Main(string[] args)
{
SayNumber sayNumber;
////Binding delegate var to concrete method.
sayNumber = Say;
Console.WriteLine("Hello my number is {0}.\n", sayNumber(7));
Console.WriteLine("Hi my number is {0}.\n", sayNumber(1));
Console.ReadKey();
}
}
/// <summary>
/// 使用委托函數(shù)的實(shí)現(xiàn)
/// </summary>
class Program
{
////Define delegate type.
private delegate int SayNumber(int number);
static void Main(string[] args)
{
SayNumber sayNumber;
////Binding delegate var to concrete method.
sayNumber = delegate(int number)
{
return ++number;
};
Console.WriteLine("Hello my number is {0}.\n", sayNumber(7));
Console.WriteLine("Hi my number is {0}.\n", sayNumber(1));
Console.ReadKey();
}
}

圖4輸出結(jié)果
也許大家覺得這樣沒有太大區(qū)別,只不過是去掉函數(shù)名,然后把具體實(shí)現(xiàn)放到委托變量實(shí)例化上面,的確我覺得這樣的寫法看起來丑陋,當(dāng)有了λ表達(dá)式匿名方法就黯然失色了。
1.1.2λ表達(dá)式
相信不用介紹很多人都了解或使用過λ表達(dá)式,特別是熟識Linq的各位。OK讓我們快步進(jìn)入λ表達(dá)式的介紹。
隨著C#2.0的匿名方法到C#3.0出現(xiàn)了λ表達(dá)式,事實(shí)上如果我們先介紹λ表達(dá)式那就沒有必要再介紹匿名方法了,但是考慮到我們程序員也要前后兼容所以我們也要對匿名方法有所了解(至少我是這樣認(rèn)為的)。
讓我們看一下匿名方法和λ表達(dá)式表達(dá)式定義上的區(qū)別

圖5匿名方法和λ表達(dá)式的定義
通過上面的對比我們發(fā)現(xiàn)匿名方法和λ表達(dá)式在定義上并沒有太大的區(qū)別,匿名方法要使用關(guān)鍵字delegate,而λ表達(dá)式使用 =>“go to”操作符,在λ表達(dá)式中編譯器將根據(jù)我們定義委托類型去推斷出表達(dá)式參數(shù)類型和返回值,這使得我們可以使用更加簡潔的方式去實(shí)例化委托變量。接下來我們看看λ表達(dá)式可以有多簡潔吧!

圖6λ表達(dá)式的定義
通過上圖我們發(fā)現(xiàn)λ表達(dá)式甚至可以省去參數(shù)類型,它通過編譯器推斷委托類型來判斷參數(shù)類型和前面提及的返回值類型推斷是一樣的原理。
λ表達(dá)式的三部曲
λ表達(dá)式中參數(shù)個數(shù)和類型必需和委托類型匹配
λ表達(dá)式中的參數(shù)可以使用隱式定義,但委托類型中參數(shù)帶有ref或out必需使用參數(shù)顯式定義
λ表達(dá)式如果沒有傳遞參數(shù),那么只需用“()”表示即可

圖7λ表達(dá)式
1.1.3委托與事件的進(jìn)階
在前面的博文我簡單的提及了事件和委托之間的關(guān)系和實(shí)現(xiàn),而且大家對于事件也再熟悉不過了特別是在windows消息機(jī)制下。大家都知道我們可以自定義委托類型,指定參數(shù)個數(shù)、類型和返回值類型。但.NET也提供我們一種已經(jīng)定義好的委托類型--EventHandler 是一個預(yù)定義的委托。
//預(yù)定義委托的定義
public delegate void EventHandler(object sender, EventArgs e);
在解析什么是預(yù)定義委托,先講一下事件處理程序委托。
事件處理程序委托的標(biāo)準(zhǔn)簽名定義一個沒有返回值的方法,其第一個參數(shù)的類型為 Object,它引用引發(fā)事件的實(shí)例,第二個參數(shù)從 EventArgs 類型派生,它保存事件數(shù)據(jù)。如果事件不生成事件數(shù)據(jù),則第二個參數(shù)只是 EventArgs 的一個實(shí)例。否則,第二個參數(shù)為從 EventArgs 派生的自定義類型,提供保存事件數(shù)據(jù)所需的全部字段或?qū)傩浴?/p>

圖8預(yù)定義委托
EventHandler 是一個預(yù)定義的委托,專用于表示不生成數(shù)據(jù)的事件的事件處理程序方法。如果事件生成數(shù)據(jù),則必須提供自己的自定義事件數(shù)據(jù)類型,并且必須要么創(chuàng)建一個委托,其中第二個參數(shù)的類型為自定義類型,要么使用泛型 EventHandler 委托類并用自定義類型替代泛型類型參數(shù)。
EventHandler 是一個預(yù)定義的委托,專用于表示不生成數(shù)據(jù)的事件的事件處理程序方法。如果事件生成數(shù)據(jù),則必須提供自己的自定義事件數(shù)據(jù)類型,并且必須要么創(chuàng)建一個委托,其中第二個參數(shù)的類型為自定義類型,要么使用泛型 EventHandler 委托類并用自定義類型替代泛型類型參數(shù)。
接下讓我們實(shí)現(xiàn)自定義的預(yù)定義委托EventHandler<T>,首先定義一個MessageEventArgs繼承于EventArgs。
/// <summary>
/// Custom EventArgs.
/// Include only one properity "Message".
/// </summary>
public class MessageEventArgs : EventArgs
{
private string message;
public string Message
{
get { return message; }
set { message = value; }
}
/// <summary>
/// The constructor.
/// </summary>
/// <param name="message"></param>
public MessageEventArgs(string message)
{
this.message = message;
}
}
?
然后再定義一個泛型事件委托,的確很簡單就可以實(shí)現(xiàn)一個自定義事件處理程序委托。
?
/// <summary>
/// Custom generic EventHandler.
/// </summary>
public static event EventHandler<MessageEventArgs> mybuttonEvent;
現(xiàn)在我們已經(jīng)有了事件處理程序委托了,然后我們要通過事件去調(diào)用我們的事件委托,它再去調(diào)用具體實(shí)現(xiàn),這里我們通過自定義一個Button控件,然后再重寫OnClick方法去調(diào)用我們自定義事件委托。
/// <summary>
/// Custom button control.
/// </summary>
public partial class WFCLButton : Button
{
public static event EventHandler<MessageEventArgs> mybuttonEvent;
public WFCLButton()
{
InitializeComponent();
}
protected override void OnClick(EventArgs e)
{
////Whether mybuttonEvent is not null, invoking event.
if (mybuttonEvent != null)
{
mybuttonEvent(this, new MessageEventArgs("This is custom event args.\n"));
}
base.OnClick(e);
}
}
?
現(xiàn)在我們有了自定義Button控件,而且重寫了OnClick方法當(dāng)發(fā)現(xiàn)點(diǎn)擊時候我們就去調(diào)用自定義事件委托,現(xiàn)在我們往Form里面添加自定義Button控件,然后
在Form的構(gòu)造函數(shù)里面添加委托變量,并且綁定事件委托具體實(shí)現(xiàn)的方法名。
?
WFCL.WFCLButton.mybuttonEvent +=
new EventHandler<MessageEventArgs>(Custom_ButtonClick);
?
?
如上代碼實(shí)現(xiàn)了事件委托和具體實(shí)現(xiàn)的關(guān)聯(lián),而且注意我使用的是靜態(tài)事件委托。一下是事件委托具體調(diào)用方法。
?
private void Custom_ButtonClick(object sender, MessageEventArgs e)
{
MessageBox.Show(e.Message);
}


圖9自定義事件委托
這樣我們就可以自定義事件處理程序委托,但細(xì)心的大家肯定發(fā)現(xiàn)我是通過一個間接地方法實(shí)現(xiàn)的,為什么這樣說呢?

圖10預(yù)定義委托
大家看到這是什么,響應(yīng)Click事件委托的是預(yù)定義委托,并不是我們自定義的事件委托,如果我們自己把EventHandler改為EventHandler<MessageEventArgs>,這時候就會出現(xiàn)以下錯誤信息。

圖11自定義事件委托
無法將類型“System.EventHandler<WFCL.MessageEventArgs>”隱式轉(zhuǎn)換為“System.EventHandler”
首先我們要找到是否存在泛型Click事件委托,如果沒有泛型Click事件委托那我們肯定不能這樣去初始化事件委托變量。我們可以在.NET Framework中的Control中找到Click的定義,里面并沒有泛型定義,所為我們不能通過上面的方法實(shí)現(xiàn)事件委托,而是通過我上面介紹的間接方式實(shí)現(xiàn)事件委托。這也充分說明一點(diǎn)自定義委托只能通過間接手段實(shí)現(xiàn)(如果各位大大有直接實(shí)現(xiàn)方式告訴我謝謝)。

圖12 Click事件委托
通過上面的類圖我們發(fā)現(xiàn)Click事件委托是在Control類中定義,而我們自定義Button控件是通過繼承Button類來實(shí)現(xiàn)的,查看Control類Click事件委托如下,然后通過調(diào)用AddHandler來保存委托實(shí)例。
/// <summary>
/// 點(diǎn)擊事件委托定義
/// </summary>
public event EventHandler Click
{
add
{
base.Events.AddHandler(EventClick, value);
}
remove
{
base.Events.RemoveHandler(EventClick, value);
}
}
|
|
關(guān)于作者:[作者]:
JK_Rush從事.NET開發(fā)和熱衷于開源高性能系統(tǒng)設(shè)計(jì),通過博文交流和分享經(jīng)驗(yàn),歡迎轉(zhuǎn)載,請保留原文地址,謝謝。 |

本文主要介紹.NET中的委托與事件,加深委托的學(xué)習(xí),對前一博文沒有涉及到的匿名方法、Lambda表達(dá)式、EventHandle,EventHandle及事件和委托進(jìn)階。
浙公網(wǎng)安備 33010602011771號