今天一來(lái)是有點(diǎn)空,二來(lái)是在博客上偶然看到有關(guān)于委托的文章,一時(shí)興起,就自己也寫一點(diǎn)心得與大家分享一下。
先看一個(gè)例子:
namespace ConsoleApplication1
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
bool m_isRight = false;
object m_obj = m_isRight?MyWrite("true"):MyWrite("false");
Console.Write(m_obj);
}
static private int MyWrite(object i_string)
{
Console.Write(i_string);
return i_string.ToString().Length;
}
}
}
問(wèn)輸出的結(jié)果是什么?有一個(gè)剛學(xué)習(xí)程序設(shè)計(jì)不久的學(xué)生的回答是:
false false這個(gè)結(jié)果給我的映像很深,為什么呢?因?yàn)槲矣X得這個(gè)不僅僅是學(xué)生的一個(gè)錯(cuò)誤,而更多的是這個(gè)學(xué)生深入的思考了問(wèn)題。
因?yàn)?/SPAN>m_obj是一個(gè)對(duì)象,所以這個(gè)學(xué)生理解為:MyWrite()這個(gè)函數(shù)對(duì)象可以直接賦值給m_obj,然后m_obj就當(dāng)成MyWrite()這個(gè)函數(shù)來(lái)調(diào)用,所以他就認(rèn)為:
Console.Write (m_obj); 等于是:Console.Write (MyWrite(“false”));
這是思維是很有創(chuàng)意的,不是嗎?
于是就是C#里而很多人不好理解的委托了。其實(shí),從使用上講,它就是一個(gè)函數(shù)變量!如上面的例子,如果真的是想把MyWrite()做為對(duì)象賦值給m_obj會(huì)是個(gè)什么結(jié)果呢?
我覺得我們先得解決以下幾個(gè)問(wèn)題,才能正確的把函數(shù)當(dāng)成變量賦值給一個(gè)對(duì)象:
1、 如果可以給一個(gè)對(duì)象賦函數(shù)值,如何來(lái)區(qū)別不同的函數(shù)?
2、 如何區(qū)別它是一個(gè)函數(shù)賦值,還是一個(gè)普通的對(duì)象賦值?
3、 如何用這個(gè)對(duì)象來(lái)調(diào)用原來(lái)的函數(shù)?
如果把這幾個(gè)問(wèn)題解決了,委托也就明白了一半。
先看問(wèn)題1,如果可以給一個(gè)對(duì)象賦函數(shù)值,如何來(lái)區(qū)別不同的函數(shù)?
首先應(yīng)該明白的是:C#里是可以對(duì)一個(gè)對(duì)象賦函數(shù)值的。解決這個(gè)問(wèn)題的辦法是先對(duì)該對(duì)象申明,申明它可以被什么樣的函數(shù)來(lái)賦值,而這個(gè)對(duì)象申明在C#里的學(xué)名就是委托。
(在C++里稱為函數(shù)指針申明,相應(yīng)的對(duì)象也就叫做函數(shù)指針。Java里也不同的叫法,可惜我不知道。)
而它的語(yǔ)法就是:
delegate [function declare];
這里的function declare就包括了:
1、 函數(shù)返回類型,
2、 可用來(lái)存放函數(shù)的對(duì)象名(也就是委托名)
3、 函數(shù)參數(shù)
所以完整的定義可以是:
delegate int MyDelegate(object I_object);
當(dāng)然,合法的委托定義可以是:
delegate void MyDelegate();
delegate void MyDelegate(object I_1,object I_2);
…
現(xiàn)在,上面的語(yǔ)法就定義了一個(gè)抽象的對(duì)象MyDelegate, 注意,這里說(shuō)的是抽象的對(duì)象,也就是說(shuō),你不能直接給MyDelegate賦函數(shù),而只能在它的實(shí)例上函數(shù),這是C#里特殊的要求。它的語(yǔ)法是:
MyDelegate m_delegate = new MyDelegate(與MyDelegate申明一致的函數(shù)名);
例如,以下是一個(gè)完全的,合法的委托申明與實(shí)例化一個(gè)對(duì)象:
//
MyDelegate m_delegate = new MyDelegate(MyWrite);
//MyWrite函數(shù)如下,它是滿足委托的申明的。
static private int MyWrite(object i_string)
{
Console.Write(i_string);
return i_string.ToString().Length;
}
現(xiàn)在我們就很好的解決了第一個(gè)問(wèn)題,如何定義一個(gè)對(duì)象,使該對(duì)象可以把函數(shù)當(dāng)變量來(lái)賦給它。而且,可以區(qū)別不同的函數(shù)類型,主要是通過(guò)函數(shù)返回值與函數(shù)參數(shù)來(lái)共區(qū)別一類函數(shù)。
OK,第二個(gè)問(wèn)題:如果有了這樣的一個(gè)對(duì)象后,如何來(lái)給它賦一個(gè)函數(shù)值呢?
其實(shí)上面的實(shí)例化一個(gè)委托對(duì)象時(shí),就已經(jīng)給它賦值了。上面的代碼中,m_delegate就已經(jīng)被賦值MyWrite,因此它已經(jīng)具有了MyWrite函數(shù)的功能。
還有其實(shí)它的方法來(lái)給它賦值嗎?有,在委托的一個(gè)應(yīng)用中,可以看到其它的賦值方法。也就是另一個(gè)不好理解的概念:事件!后面會(huì)提到。
我們?cè)賮?lái)看一下最后一個(gè)問(wèn)題:如何通過(guò)一個(gè)已經(jīng)賦值好了的委托對(duì)象,還調(diào)用它上面賦值了的函數(shù)。
這個(gè)最簡(jiǎn)單了,當(dāng)一個(gè)委托實(shí)例賦了函數(shù)對(duì)象在上面后,就可以像調(diào)用原函數(shù)一樣的來(lái)調(diào)用它了。因此,下面是一個(gè)會(huì)法的調(diào)用:基于上面的申明。
m_delegate(“This is a delegate object to call the raw function.”);
它就等同于:
MyWrite(“This is a delegate object to call the raw function.”);
因此,上面的調(diào)用與原函數(shù)調(diào)用一樣,會(huì)返回一個(gè)int結(jié)果。
OK,最后看一個(gè)完整的例子:
namespace ConsoleApplication1
{
class Class1
{
//先申明一個(gè)委托對(duì)象。
delegate int MyDelegate(object i_object);
[STAThread]
static void Main(string[] args)
{
MyDelegate m_delegate = new MyDelegate(MyWrite);
m_delegate("This is a delegate object to call the raw function.");
}
//該函數(shù)是滿足上面委托對(duì)象的申明的。
static private int MyWrite(object i_string)
{
Console.Write(i_string);
return i_string.ToString().Length;
}
}
}
事件是其于委托的。我們還是先來(lái)看最開始的那個(gè)例子:
object m_obj = m_isRight?MyWrite("true"):MyWrite("false");
我想把一個(gè)函數(shù)對(duì)象賦值到m_obj上!但上面的委托只能在實(shí)例化對(duì)象的時(shí)候就直接給它賦值了。而現(xiàn)在是,在運(yùn)行時(shí)對(duì)一個(gè)委托賦函數(shù)值?梢宰龅絾?
同樣是有這樣的向個(gè)問(wèn)題,當(dāng)然,前提是我們已經(jīng)知道有一種對(duì)象叫委托,它的實(shí)例可以賦函數(shù)對(duì)象。
下面的問(wèn)題是:
1、 如果可以在運(yùn)行時(shí)給某個(gè)“特殊委托”賦函數(shù)對(duì)象,如何實(shí)現(xiàn)?
2、 運(yùn)行時(shí),如何知道該“特殊委托”是否已經(jīng)被賦過(guò)函數(shù)值?及如何再賦值?
3、 如果可以,能否在一個(gè)“特殊委托”上添加多個(gè)函數(shù)?如果可以,如何刪除函數(shù)?
下面,我們就針對(duì)這幾個(gè)問(wèn)題,來(lái)討論一下C#里的事件,也就是上面的“特殊委托”。(其它語(yǔ)言里是如何實(shí)現(xiàn)這些功能的,我就不清楚了。)
首先,C#里是可以實(shí)現(xiàn)在運(yùn)行時(shí)給一個(gè)委托動(dòng)態(tài)的賦函數(shù)值的,同時(shí)也是可以動(dòng)態(tài)的刪除已經(jīng)添加在某個(gè)委托上的函數(shù)的,它的實(shí)現(xiàn)有一點(diǎn)點(diǎn)麻煩,就是要用到另一個(gè)對(duì)象:事件!event
(申明,你完全可以不把它叫事件,只不過(guò)這種動(dòng)態(tài)的添加和刪除函數(shù)的功能在真實(shí)的程序設(shè)計(jì)中,基本上是與事件相關(guān),所以就叫做事件了。個(gè)人想法,呵呵。)
OK,下面是C#語(yǔ)法,來(lái)申明一個(gè)“特殊委托”――事件,讓它可以動(dòng)態(tài)的添加函數(shù)!
下文中,事件是指那些“特殊委托”,它的特殊之外,后面會(huì)講到。而下文中的委托就是前面講到的,一個(gè)特殊的對(duì)象,該對(duì)象可以把函數(shù)當(dāng)“值”賦給它。
static event MyDelegate m_myevent;
(static 可以用其它的修飾符)
說(shuō)明一下,這里其實(shí)就是申明了一個(gè)事件,用event來(lái)說(shuō)明它是事件(特殊委托)的。其實(shí)對(duì)比實(shí)例化一個(gè)委托的語(yǔ)法,你可以理解到,它就像是申明了一個(gè)委托,只不過(guò)個(gè)委托加了個(gè)event來(lái)說(shuō)明它:
Mydelegate m_delegate;//申明一個(gè)委托
event MyDelegate m_myevent;//申明一個(gè)事件
很像吧!不是嗎?
OK,我們?cè)賮?lái)看,m_myevent 與m_delegate到底有什么不同的?也就是,事件(特殊委托)到底特殊在什么地方?
1、 事件不是一個(gè)可以直接把函數(shù)當(dāng)值一樣賦給它的委托。而委托可以直接賦函數(shù),而且是在實(shí)例化的時(shí)候,賦函數(shù)名。
2、 事件只能把一個(gè)實(shí)例的委托當(dāng)值賦給它。也就是說(shuō):事件是用來(lái)管理委托的,進(jìn)而來(lái)管理函數(shù)!因?yàn)橐粋(gè)實(shí)例化的委托一定有一個(gè)函數(shù)與之對(duì)應(yīng)。
3、 在事件上可以動(dòng)態(tài)的添加與刪除委托。而委托上不能動(dòng)態(tài)的添加刪除函數(shù)。
OK,下面的一個(gè)問(wèn)題,上面事件的申明中,MyDelegate是起什么作用的呢?
還記得前面的委托申明嗎?它就是說(shuō)明了m_myevent在運(yùn)行時(shí)可以動(dòng)態(tài)的以委托的形式賦的函數(shù)要與MyDelegate申明的一樣!
因此上面的一個(gè)實(shí)例化是完全合法的。
再理解一下:事件,是用來(lái)動(dòng)態(tài)管理委托的,而委托是單一的與一個(gè)函數(shù)對(duì)應(yīng)的。
現(xiàn)在看第二個(gè)問(wèn)題,運(yùn)行時(shí),如何知道該“特殊委托”是否已經(jīng)被賦過(guò)函數(shù)值?及如何再賦值?
即:如何知道一個(gè)事件上已經(jīng)賦過(guò)經(jīng)過(guò)委托過(guò)的函數(shù)?
前面已經(jīng)說(shuō)過(guò),m_myevent沒有給它賦值,如何給它賦值呢?它的賦值方法有點(diǎn)怪:
一個(gè)實(shí)例:
m_myevent += m_delegate;
有點(diǎn)怪吧!這里正好說(shuō)明了:事件是用來(lái)動(dòng)態(tài)管理委托的。把一個(gè)委托加在事件上。
當(dāng)然,你還可以在添加委托的時(shí)候直接new一個(gè)新的委托:
m_myevent +=new MyDelegate(Class1_m_myevent);//后面的函數(shù)名由vs2003生動(dòng)生成
這就是.net下標(biāo)準(zhǔn)的給事件添加委托的方法,也就是給一個(gè)事件添加了一個(gè)可調(diào)用的函數(shù)。確切的說(shuō),是一個(gè)回調(diào)函數(shù)(C++的概念)。
OK,下面就如何判斷一個(gè)事件上是否已經(jīng)被賦過(guò)經(jīng)過(guò)委托的函數(shù):
if(m_myevent==null)
就可以知道了!
那么如何知道一個(gè)事件上面有多少個(gè)委托呢?也就是多少個(gè)委托過(guò)的函數(shù)?
m_ myevent.GetInvocationList();
可以得到所有的委托!這里應(yīng)該知道:事件本身是一個(gè)對(duì)象,當(dāng)實(shí)例化一個(gè)事件后,它是有自己的一些方法也成員的。可以查閱MSDN得到更多說(shuō)明,同樣的委托也是一個(gè)對(duì)象,相關(guān)的說(shuō)明也可以在MSDN里找到。
最后的問(wèn)題:如何刪除一個(gè)已經(jīng)添加在事件上的委托?
太容易而且太有意思了:
m_myevent -= m_delegate;
那么這樣的幾個(gè)問(wèn)題又來(lái)了:
1. 如果這個(gè)事件上沒有該委托,“減掉”以后會(huì)出錯(cuò)嗎?不會(huì),放心的減吧。
2. 如何調(diào)用這個(gè)事件上的委托呢?上面有多個(gè)委托,它是怎樣運(yùn)行呢?
調(diào)用事件上的委托與調(diào)用委托上的函數(shù)是完全一樣的:你要給出與委托申明一樣的函數(shù)參數(shù),并且調(diào)用會(huì)返回與申明一樣的數(shù)據(jù)類型。
最后再回來(lái)看這個(gè)問(wèn)題:
object m_obj = m_isRight?MyWrite("true"):MyWrite("false");
如何解決它呢?把一個(gè)函數(shù)賦給一個(gè)對(duì)象:一個(gè)例示(僅做演示解決這個(gè)問(wèn)題,個(gè)人認(rèn)為這樣的做法沒有實(shí)用意義)
namespace ConsoleApplication1
{
public class Class4
{
event System.EventHandler m_obj;
public Class4()
{
System.EventHandler m_f1 = new EventHandler(SomeFunc1);
System.EventHandler m_f2 = new EventHandler(SomeFunc2);
bool m_someCondition = false;
m_obj += m_someCondition?m_f1:m_f1;
m_obj(this,null);
}
private void SomeFunc1(object sender, EventArgs args)
{
}
private void SomeFunc2(object sender, EventArgs args)
{
}
}
}
最后看一個(gè)完整的例子:
namespace ConsoleApplication1
{
class Class1
{
//先申明一個(gè)委托對(duì)象。
delegate int MyDelegate(object i_object);
static event MyDelegate m_myevent;
[STAThread]
static void Main(string[] args)
{
MyDelegate m_delegate = new MyDelegate(MyWrite);
m_delegate("This is a delegate object to call the raw function.");
m_myevent += m_delegate;
m_myevent += new MyDelegate(MyWrite);
m_myevent +=new MyDelegate(Class1_m_myevent);
if(m_myevent!=null)
{
m_myevent("This is a event to call the funcaion on the delegate.");
}
}
//該函數(shù)是滿足上面委托對(duì)象的申明的。
static private int MyWrite(object i_string)
{
Console.WriteLine(i_string);
return i_string.ToString().Length;
}
private static int Class1_m_myevent(object i_object)
{
Console.WriteLine(i_object);
return 0;
}
}
}
namespace ConsoleApplication1
{
public delegate void MyEventHandle(object i_sender,object i_arg);
public class Class2
{
public Class2()
{
}
[STAThread]
static void Main2(string[] args)
{
Class3 m_runOject = new Class3();
m_runOject.OnError += new MyEventHandle(m_runOject_OnError);
m_runOject.OnSomeThingHappened += new MyEventHandle(m_runOject_OnSomeThingHappened);
m_runOject.Run();
}
private static void m_runOject_OnError(object i_sender, object i_arg)
{
Console.WriteLine("Error in {0}, arg:{1}",i_sender,i_arg);
Console.WriteLine("Object {0} will stop running.",i_sender);
(i_sender as Class3).Stop();
}
private static void m_runOject_OnSomeThingHappened(object i_sender, object i_arg)
{
Console.WriteLine("Something happended in {0}, arg:{1}",i_sender,i_arg);
}
}
public class Class3
{
public bool m_isStop = false;
public event MyEventHandle OnSomeThingHappened;
public event MyEventHandle OnError;
public Class3()
{
}
public void Run()
{
Random m_rand = new Random();
int m_randomNum = m_rand.Next();
while(!m_isStop)
{
if(m_isStop){break;}
m_randomNum = m_rand.Next(100);
if(m_randomNum%5==0)
{
if(this.OnError!=null)
{
this.OnError(this,m_randomNum);
}
}
else
{
if(this.OnSomeThingHappened!=null)
{
this.OnSomeThingHappened(this,m_randomNum);
}
}
}
}
public void Stop()
{
m_isStop = true;
}
}
}
好了,全部完了!
最后再?gòu)牧硪粋(gè)角度來(lái)理解:函數(shù),委托和事件!
1、 函數(shù),是程序的基本單元,在.net下,有一個(gè)很重要的思想,就是:一切對(duì)象化!你可把一個(gè)int boxing后得到一個(gè)object,也可以把一個(gè)object unboxing后得到一個(gè)int. 可惜,就是沒有函數(shù)對(duì)象化這個(gè)概念?
2、 其實(shí)函數(shù)也可以對(duì)象化!就是通過(guò)delegate,委托就是把函數(shù)作為對(duì)象來(lái)處理,使它函數(shù)也具有了對(duì)象的一些特點(diǎn)。在實(shí)例化一個(gè)委托的時(shí)候,就是把一個(gè)函數(shù)”boxing”。因此,委托本質(zhì)上就是一個(gè)類!它的初始化參數(shù)是一個(gè)函數(shù)名!不同的委托是不同的類,對(duì)應(yīng)不同類型的函數(shù)。
3、事件是對(duì)委托的一個(gè)管理封裝,它可以很好的動(dòng)態(tài)管理委托,從而完成很多有實(shí)用價(jià)值的事情,最主要的就是事件!
完全是個(gè)人理解,有不同意見,歡迎討論!
原文地址:http://www.cnblogs.com/WuCountry/archive/2006/11/29/576030.html