日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > C# >内容正文

C#

浅谈C#委托和事件

發布時間:2024/4/17 C# 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 浅谈C#委托和事件 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

委托給了C#操作函數的靈活性,我們可使用委托像操作變量一樣來操作函數,其實這個功能并不是C#的首創,早在C++時代就有函數指針這一說法,而在我看來委托就是C#的函數指針,首先先簡要的介紹一下委托的基本知識:

委托的定義
委托的聲明原型是?
delegate?<函數返回類型>?<委托名> (<函數參數>)
例子:public delegate void CheckDelegate(int number);//定義了一個委托CheckDelegate,它可以注冊返回void類型且有一個int作為參數的函數
這樣就定義了一個委托,但是委托在.net內相當于聲明了一個類(在后面的代碼中會講到確實如此),類如果不實例化為對象,很多功能是沒有辦法使用的,委托也是如此.

委托的實例化
委托實例化的原型是
<委托類型> <實例化名>=new <委托類型>(<注冊函數>)
例子:CheckDelegate?_checkDelegate=new?CheckDelegate(CheckMod);//用函數CheckMod實例化上面的CheckDelegate?委托為_checkDelegate
在.net 2.0開始可以直接用匹配的函數實例化委托:
<委托類型> <實例化名>=<注冊函數>
例子:CheckDelegate?_checkDelegate=CheckMod;//用函數CheckMod實例化上面的CheckDelegate?委托為_checkDelegate
現在我們就可以像使用函數一樣來使用委托了,在上面的例子中現在執行_checkDelegate()就等同于執行CheckMod(),最關鍵的是現在函數CheckMod相當于放在了變量當中,它可以傳遞給其它的CheckDelegate引用對象,而且可以作為函數參數傳遞到其他函數內,也可以作為函數的返回類型

?

用匿名函數初始化委托

上面為了初始化委托要定義一個函數是不是感覺有點麻煩,另外被賦予委托的函數一般都是通過委托實例來調用,很少會直接調用函數本身。

在.net 2.0的時候考慮到這種情況,于是匿名函數就誕生了,由于匿名函數沒有名字所以必須要用一個委托實例來引用它,定義匿名函數就是為了初始化委托

匿名函數初始化委托的原型:

<委托類型> <實例化名>=new <委托類型>(delegate(<函數參數>){函數體});

當然在.net 2.0后可以用:

<委托類型> <實例化名>=delegate(<函數參數>){函數體};

例子:

??????? delegate?void?Func1(int?i);
????????delegate?int?Func2(int?i);

????????static?Func1?t1?=new?Func1(delegate(int?i)
????????{
????????????Console.WriteLine(i);
????????});

????????static?Func2?t2;

????????static?void?Main(string[]?args)
????????{
????????????t2?=?delegate(int?j)
????????????{
????????????????return?j;
????????????};
????????????t1(2);
????????????
????????????Console.WriteLine(t2(1));
????????????
????????}

?

當然在.net 3.0的時候又有了比匿名函數更方便的東西lambda表達式,這兒就不說了。


泛型委托
委托也支持泛型的使用
泛型委托原型:
delegate?<T1>?<委托名><T1,T2,T3...> (T1 t1,T2 t2,T3 t3...)
例子:
delegate T2 A<T1,T2>(T1 t);//定義有兩個泛型(T1,T2)的委托,T2作為委托函數返回類型,T1作為委托函數參數類型

static int test(int t)
{
??????return t;
}

static void Main(string[] args)
{
??????A<int, int> a =test;//將泛型委托委托<T1,T2>實例化為<int,int>,即表示有一個int類型參數且返回類型是int的函數,所以將test用來實例化委托
??????Console.WriteLine(a(5));//輸出5
}



委托的多播性
在上面實例化委托的時候看到:必須將一個匹配函數注冊到委托上來實例化一個委托對象,但是一個實例化委托不僅可以注冊一個函數還可以注冊多個函數,注冊多個函數后,在執行委托的時候會根據注冊函數的注冊先后順序依次執行每一個注冊函數
函數注冊委托的原型:
<委托類型> <實例化名>+=new <委托類型>(<注冊函數>)
例子:CheckDelegate?_checkDelegate=new?CheckDelegate(CheckMod);//將函數CheckMod注冊到委托實例_checkDelegate上
在.net 2.0開始可以直接將匹配的函數注冊到實例化委托:
<委托類型> <實例化名>+=<注冊函數>
例子:CheckDelegate?_checkDelegate+=CheckMod;//將函數CheckMod注冊到委托實例_checkDelegate上
之后我們還可以注冊多個函數到委托上:
例子:_checkDelegate+=CheckPositive;//將函數CheckPositive注冊到委托實例_checkDelegate上
????????_checkDelegate();//執行這個委托實例會先執行CheckMod()再執行CheckPositive()

實際上使用+=符號的時候會判斷
如果此時委托還沒有實例化(委托實例為null),它會自動用+=右邊的函數實例化委托
如果此時委托已經實例化,它會只把+=右邊的函數注冊到委托實例上
另外有一點需要注意的是,如果對注冊了函數的委托實例從新使用=號賦值,相當于是重新實例化了委托,之前在上面注冊的函數和委托實例之間也不再產生任何關系,后面的例子會講到這點!

當然有+=注冊函數到委托,也有-=解除注冊
例子:_checkDelegate-=new?CheckDelegate(CheckPositive);//解除CheckPositive對_checkDelegate的注冊
????????_checkDelegate-=CheckPositive;//.net 2.0開始可以用這種方式解除注冊

?

?

另外當在委托和事件(事件的細節將在后面介紹)上注冊了多個函數后,如果委托和事件有返回值,那么調用委托和事件時,返回的將是最后一個注冊函數的返回值。如下示例代碼將做詳細解釋。

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace MultiDelegatesReturn {public delegate int DelMath(int i);//定義委托類DelMath,該委托傳入一個int類型參數,返回一個int類型參數class Program{static DelMath dMath;//通過委托類型DelMath定義委托實例dMathstatic event DelMath eMath;//通過委托類型DelMath定義事件實例eMath/// <summary>/// 將傳入的參數i自加后作為函數返回值/// </summary>static int IncMath(int i){i++;Console.WriteLine("IncMath has been invoked!");return i;}/// <summary>/// 將傳入的參數i自減后作為函數返回值/// </summary>static int DecMath(int i){i--;Console.WriteLine("DecMath has been invoked!");return i;}static void Main(string[] args){int i = 10;//定義int型變量i,初始值為10dMath += IncMath;//先將IncMath函數注冊到委托實例dMathdMath += DecMath;//再將DecMath函數注冊到委托實例dMathConsole.WriteLine("dMath returned:" + dMath(i).ToString());//將int型變量10傳入委托實例dMath調用后,返回的結果是9,說明委托實例//dMath返回的是后注冊的函數DecMath的返回值eMath += IncMath;//先將IncMath函數注冊到事件實例eMatheMath += DecMath;//再將DecMath函數注冊到事件實例eMathConsole.WriteLine("eMath returned:" + eMath(i).ToString());//將int型變量10傳入事件實例eMath調用后,返回的結果也是9,說明事件實例//eMath返回的也是后注冊的函數DecMath的返回值}} }

?

?

?


c#事件
了解委托之后,就可以來談談事件了,C#事件是什么?
c#事件的定義和委托的聲明是如此的相似:
event <委托類型> 事件名
例子:public event CheckDelegate checkEvent;
上面的例子聲明了個事件叫checkEvent你會發現它只比聲明委托實例前多了個關鍵字event
聲明了事件后就可以實例化事件,注冊函數到事件,解除事件函數注冊其方法和委托的步驟如出一轍:
例子:checkEvent+=new?CheckDelegate(CheckMod);//將函數CheckMod注冊到事件checkEvent上
?????? checkEvent+=CheckMod;//.net 2.0開始支持這種方法
???????checkEvent-=new?CheckDelegate(CheckMod);//將函數CheckMod解除對事件checkEvent的注冊
?????? checkEvent-=CheckMod;//.net 2.0開始支持這種方法

從種種跡象都可以看出事件和委托實例是那么的相似,那么為什么不直接用委托還要用到事件呢?其實事件就是對委托的封裝,就如同c#類中屬性對字段的封裝一樣,其封裝后可以在委托上封裝更復雜的邏輯,下面我們來看c#中事件的兩種聲明方式,來了解事件對委托的封裝

隱式聲明事件
這種方式聲明事件很簡單,就如同聲明委托實例一樣:
event <委托類型> 事件名;
例子:public event CheckDelegate checkEvent;
我們用反射機制來看看這樣聲明的事件里面裝的到底是什么東西

我們可以看到在事件被編譯后自動生成了個private的委托實例checkEvent和兩個函數add_checkEvent和remove_checkEvent,這兩個函數分別對應事件的+=/-=操作,另外可以看到在聲明了事件后的確是產生了一個和事件同名私有的委托實例checkEvent,對事件的+=/-=操作都會反映在這個同名委托實例checkEvent上,所以可以在定義事件的類里面直接調用checkEvent()來執行注冊函數和對checkEvent使用=號重新賦值,實際上這里操作的并不是checkEvent事件,而操作的是同名委托實例checkEvent,因此隱式聲明的事件,其實就是由一個委托實例和兩個函數封裝而成,所有的操作最終都反映在委托實例上。

(這里我補充下我的個人理解:事實上在一個類的內部是無法定義一個事件后又定義一個和事件同名的委托實例的,如果你在本例中嘗試再定義CheckDelegate checkEvent,編譯的時候會報錯并提示已經定義了名叫checkEvent的委托,原因是因為事件本來就是一種特殊的委托實例(不管是隱式或顯式聲明的事件都是這樣),因此定義和事件同名的委托實例會報錯,所以我個人認為.net在編譯的時候會把隱式聲明的事件編譯成為委托實例(和事件同名),本例中的checkEvent事件在編譯后也不再是事件轉而被編譯成了checkEvent委托實例,否則又怎么可能在定義事件的類的內部可以執行事件和對事件賦值呢(這里大家可以看看我給的顯式聲明事件的例子,那里面有說到),唯一的解釋就是隱式聲明的事件其實就是委托實例)

顯式聲明事件
其實顯示聲明事件就是要自己來手動實現隱式聲明事件的一個委托實例
和兩個函數:
event <委托類型> 事件名
{
??????add
??????{
????????????//將函數注冊到自己定義的委托實例
??????}

??????remove
??????{
????????????//解除函數對自己定義的委托實例的注冊
??????}
}

例子:private CheckDelegate _checkDelete;
??????? public event CheckDelegate checkEvent
??????? {
??????????? add
??????????? {
??????????????? _checkDelete = Delegate.Combine(_checkDelete, value) as CheckDelegate;
??????????? }
??????????? remove
??????????? {
??????????????? _checkDelete = Delegate.Remove(_checkDelete, value) as CheckDelegate;
??????????? }
??????? }
//Delegate.Combine和Delegate.Remove是.net庫函數,作用是合并委托實例注冊函數和移除委托實例注冊函數并返回合并和移除后的委托實例,具體解釋請查閱MSDN

我們再用反射機制查看顯式聲明事件編譯后的代碼

可以看到顯示聲明事件的代碼編譯后和隱式聲明事件的代碼幾乎相同,只不過這里我們自己定義了事件操作委托實例_checkDelete。另外顯式聲明的事件不支持直接調用,就算在定義事件的類里面也不能直接調用顯式聲明的事件(checkEvent();//這樣會報錯),應該調用事件委托實例(_checkDelete();)。

本文例子
俗話說得好說得多不如做得多,現在就把例子發出來,例子中還講了些東西,可以執行例子看了輸出結果后再體會:
首先是個c#類庫項目ClassLibrary,里面包含兩個類分別是顯式聲明和隱式聲明事件
AutoCheckClass.cs

using?System;
using?System.Collections.Generic;
using?System.Text;

namespace?ClassLibrary
{
????public?class?AutoCheckClass
????{
????????public?delegate?void?CheckDelegate(int?number);
????????public?event?CheckDelegate?checkEvent;

????????public?void?WriteInner(int?n)
????????{
????????????Console.WriteLine(n.ToString());
????????}

????????public?void?InitEvent()
????????{
????????????checkEvent?=?WriteInner;//對事件從新賦值
????????????//checkEvent?=?new?CheckDelegate(WriteInner);//也可以用委托對事件進行賦值
????????}

????????public?void?Exec(int?n)
????????{
????????????checkEvent(n);
????????}

????????/*
?????????采用這種方式,public?event?CheckDelegate?checkEvent;會自動生成一個private?CheckDelegate?checkEvent,
?????????對于public?event?CheckDelegate?checkEvent;的+/-操作都會在編譯時反應在private?CheckDelegate?checkEvent上
?????????而且add/remove?.net在編譯的時候會自動生成,不用自己再操心,缺點是每個事件的委托都被封裝,無法操作其內部的委托
?????????
?????????此外采用這種方式定義的事件,可以在定義事件的類的內部直接對事件進行賦值,例如可以在Exec函數中加上下面這句代碼:
?????????checkEvent?=?Exec;
?????????表示該事件可以被匹配的函數或委托賦值初始化。
?????????并且對事件進行賦值操作,相當于從新初始化事件內部的委托(同名委托實例),會讓賦值之前對事件注冊的函數都不再與事件產生關系,具體示例請見本類中InitEvent函數的使用效果。
?????????*/
????}
}


CheckClass.cs

using?System;
using?System.Collections.Generic;
using?System.Text;

namespace?ClassLibrary
{
????public?class?CheckClass
????{
????????public?delegate?void?CheckDelegate(int?number);
????????private?CheckDelegate?_checkDelete;
????????public?event?CheckDelegate?checkEvent
????????{
????????????add
????????????{
????????????????_checkDelete?=?Delegate.Combine(_checkDelete,?value)?as?CheckDelegate;
????????????}
????????????remove
????????????{
????????????????_checkDelete?=?Delegate.Remove(_checkDelete,?value)?as?CheckDelegate;
????????????}
????????}

????????public?void?Exec(int?n)
????????{
????????????_checkDelete(n);
????????????//checkEvent?=?Exec;注意顯示定義事件的方式,不支持對事件直接進行賦值
????????}

????????/*
?????????delegate在編譯的時候會被net編譯成一個類,如下:
?????????public?delegate?void?CheckDelegate(int?number);在編譯的時候會編譯為下面的類
?????????public?sealed?class?CheckDelegate:System.MulticastDelegate
?????????{
????????????public?GreetingDelegate(object?@object,?IntPtr?method);
????????????public?virtual?IAsyncResult?BeginInvoke(string?name,?AsyncCallback?callback,?object?@object);
????????????public?virtual?void?EndInvoke(IAsyncResult?result);
????????????public?virtual?void?Invoke(string?name);
?????????}
?????????而System.MulticastDelegate繼承于System.Delegate,所以下面的代碼才會順利執行
?????????_checkDelete?=?Delegate.Combine(_checkDelete,?value)?as?CheckDelegate;
?????????_checkDelete?=?Delegate.Remove(_checkDelete,?value)?as?CheckDelegate;?
?????????采用這種方法可以讓你自己指定事件的委托,甚至可以讓多個事件使用同一個委托,且自己實現add/remove,可以實現更復雜的邏輯
?????????
?????????此外需要注意的是,采用這種方式定義的事件,就算在定義事件的類的內部都無法對事件直接進行賦值,例如先前在另外種定義方式說到的在Exec函數中加上:
?????????checkEvent?=?Exec;
?????????會報錯:事件“ClassLibrary.CheckClass.checkEvent”只能出現在?+=?或?-=?的左邊
?????????所以在這里我們不應該操作checkEvent,因為它沒有同名委托實例,而因該操作_checkDelete
?????????*/
????}
}


然后是個控制臺項目,需要引入上面的類庫的dll文件
Program.cs

using?System;
using?System.Collections.Generic;
using?System.Text;
using?ClassLibrary;

namespace?DeleGate
{
????class?Temp//定義此類是為了在代碼中展示函數對委托和事件的另外一種注冊方式
????{
????????public?delegate?void?TempDelegate(int?u);
????????public?static?TempDelegate?td;
????????public?static?event?TempDelegate?ed;
????}

????class?Program
????{
????????private?static?void?CheckMod(int?number)
????????{
????????????if?(number?%?2?==?0)
????????????????Console.WriteLine("輸入的是偶數");
????????????else
????????????????Console.WriteLine("輸入的不是偶數");
????????}

????????private?static?void?CheckPositive(int?number)
????????{
????????????if?(number?>?0)
????????????????Console.WriteLine("輸入的是正數");
????????????else
????????????????Console.WriteLine("輸入的不是正數");
????????}

????????
????????static?void?Main(string[]?args)
????????{
????????????CheckClass?cc?=?new?CheckClass();
????????????cc.checkEvent?+=?new?CheckClass.CheckDelegate(CheckMod);
????????????cc.checkEvent?+=?new?CheckClass.CheckDelegate(CheckPositive);

????????????AutoCheckClass?acc?=?new?AutoCheckClass();
????????????acc.checkEvent?+=?new?AutoCheckClass.CheckDelegate(CheckMod);
????????????acc.checkEvent?+=?new?AutoCheckClass.CheckDelegate(CheckPositive);
????????????//acc.InitEvent();//執行了這個方法后,由于對事件從新賦了值,上面對事件注冊的兩個函數都會失效
????????????
????????????Temp.td?=?CheckMod;//這表示對委托進行賦值(等同于:Temp.td?=?new?Temp.TempDelegate(CheckMod);),和對事件賦值一樣,對委托進行賦值相當于初始化委托,會讓賦值之前在委托上注冊的函數與委托失去注冊關系。
????????????Temp.td?+=?CheckPositive;
????????????Console.WriteLine("Temp的結果");
????????????Temp.td(50);

????????????Temp.ed?+=?CheckMod;
????????????Temp.ed?+=?CheckPositive;

????????????Console.WriteLine("cc的結果");
????????????cc.Exec(50);
????????????Console.WriteLine("acc的結果");
????????????acc.Exec(50);
????????????

????????????Console.ReadKey();
????????}
????}
}

?

?

附加更新補充

調用委托實例的對象并不是調用委托函數的對象

通過前面的例子,我們了解到了,委托其實就是C#中的函數指針,有了委托我們可以像使用變量一樣來使用函數。但是請切記調用委托實例的對象,絕不是調用委托函數的對象。這一點我們通過如下例子來說明.

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace DelagateInstanceCall {//DelegateContainer是定義委托類型DelMethod和委托實例delMethod的類class DelegateContainer{public delegate void DelMethod();//定義一個無參數且無返回值的委托類型DelMethodpublic DelMethod delMethod;//定義委托類型DelMethod的委托實例delMethodpublic int i = 100;//定義一個int類型的變量i在類DelegateContainer之中,賦值100}//MethodDemo是定義委托函數DisplayMethod的類class MethodDemo{protected int i = 200;//定義一個int類型的變量i在類MethodDemo之中,賦值200//定義委托函數DisplayMethodpublic void DisplayMethod(){Console.WriteLine("Varible i is : " + this.i.ToString());//顯示變量i的值,通過這里的值就可以知道委托函數DisplayMethod的調用對象是誰}}class Program{static void Main(string[] args){DelegateContainer delCon = new DelegateContainer();//構造類DelegateContainer的對象delConMethodDemo metDemo = new MethodDemo();//構造類MethodDemo的對象metDemodelCon.delMethod += metDemo.DisplayMethod;//將函數DisplayMethod注冊到委托實例delMethod,讓其作為delMethod的委托函數delCon.delMethod();//調用委托實例delMethod的時候,就會調用在它上注冊的委托函數DisplayMethod,那么在執行委托函數DisplayMethod時,其內部代碼中的this,到底指的是//委托實例delMethod的調用對象delCon呢,還是委托函數DisplayMethod的調用對象metDemo呢?//我可以看到這里輸出的結果是"Varible i is : 200",說明DisplayMethod內部的this指的是委托函數DisplayMethod本身的調用對象metDemo。這里大家很容易搞混淆,由于我們上面是通過//調用委托實例delCon.delMethod來調用委托函數metDemo.DisplayMethod的,看到delCon.delMethod()時大家潛意識可能就會認為由于調用委托實例delMethod的對象是delCon,就認為//調用委托實例delMethod上注冊函數DisplayMethod的對象也是delCon,其實這是錯誤的。大家一定要記住委托實例只是一個殼子,它只是用來代表在其上注冊的函數,但它并不會改變注冊函數//的環境變量(比如函數的調用對象等),由于我們上面將委托函數DisplayMethod注冊到委托實例delMethod時,使用的是delCon.delMethod += metDemo.DisplayMethod,所以函數的調用//對象始終都是等號右邊的對象metDemo,而不會是左邊的對象delCon,而調用等號左邊的委托實例delCon.delMethod()時,相當于就是在執行等號右邊的metDemo.DisplayMethod(),//所以委托函數DisplayMethod的調用對象始終是metDemo。//由此請大家一定要記住,調用委托實例的對象和調用委托函數的對象沒有絲毫關系,要看委托函數是誰調用的,還得要看函數注冊到委托實例時,等號右邊注冊函數前的調用對象是誰。Console.ReadKey();}} }

從上面這個例子,我們可以牢牢記住,調用委托實例的對象和調用委托函數的對象沒有絲毫關系,要看委托函數是誰調用的,還得要看函數注冊到委托實例時,等號右邊注冊函數前的調用對象是誰。這樣在使用委托時就不會出錯和弄混淆。

轉載于:https://www.cnblogs.com/yzl050819/p/6236810.html

總結

以上是生活随笔為你收集整理的浅谈C#委托和事件的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。