簡(jiǎn)單聊聊c# 事件
引言:
前面幾個(gè)專(zhuān)題對(duì)委托進(jìn)行了詳細(xì)的介紹的,然后我們?cè)诰帉?xiě)代碼過(guò)程中經(jīng)常會(huì)聽(tīng)到“事件”這個(gè)概念的,尤其是寫(xiě)UI的時(shí)候,當(dāng)我們點(diǎn)擊一個(gè)按鈕后VS就會(huì)自動(dòng)幫我們生成一些后臺(tái)的代碼,然后我們就只需要在Click方法里面寫(xiě)代碼就可以,所以可能有些剛接觸C#的朋友就覺(jué)得這樣很理所當(dāng)然的,也沒(méi)有去思考這是為什么的,為什么點(diǎn)擊下事件就會(huì)觸發(fā)我們?cè)贑lick方法里面寫(xiě)的代碼呢?事件到底扮演個(gè)什么樣的角色呢?為了解除大家的這些疑惑,下面就詳細(xì)介紹了事件,讓一些初學(xué)者深入理解C#中的事件的概念。
一、為什么C#中會(huì)有事件的?
前面專(zhuān)題中介紹了我理解的為什么需要委托的,所以這里我來(lái)分享下我理解的為什么C#中要引入事件這個(gè)概念的。下面就簡(jiǎn)單講講生活中事件的例子的,最近我生日剛過(guò)完的,我就以生日這個(gè)話題要談?wù)劦模兆右惶焯斓倪^(guò)去,當(dāng)生日的日期到的時(shí)候,這時(shí)候就觸發(fā)了生日事件的,此時(shí)過(guò)生日的人就是觸發(fā)生日事件的對(duì)象的,然后有些關(guān)系你的朋友就會(huì)對(duì)這個(gè)事件進(jìn)行關(guān)注,一旦這個(gè)事件觸發(fā), 他們就可能會(huì)陪你一起慶祝生日,然后送禮物給你,當(dāng)然并不是所有人都會(huì)對(duì)你的生日關(guān)注的,有些人肯定根本就不知道的, 只有對(duì)于你生日事件進(jìn)行了關(guān)注了的人才會(huì)送禮物給你。這樣的生活中的一個(gè)生日過(guò)程,然而對(duì)于為什么C#中會(huì)有事件這個(gè)概念當(dāng)然就更好理解了,C#是一個(gè)面向?qū)ο蟮恼Z(yǔ)言,我們使用C#語(yǔ)言進(jìn)行編碼也是為了用代碼幫助我們完成現(xiàn)實(shí)生活中的事情的,所以當(dāng)然也就必須有事件來(lái)反映生活中發(fā)生事情的情況了。
二、自己如何實(shí)現(xiàn)一個(gè)事件模式的?
現(xiàn)在我們知道了為什么C#要引入事件了,但是對(duì)于我們?cè)诖a中使用的事件大部分都是.net類(lèi)庫(kù)為我們提供的,例如控件的各種事件,我們只需要點(diǎn)擊按鈕后就會(huì)觸發(fā)點(diǎn)擊事件的,但是我們很想理解這個(gè)事件是如何觸發(fā)的,我們是否可以自己定義實(shí)現(xiàn)事件模式的一個(gè)程序的呢?答案當(dāng)然是可以的,下面就以上面生日的例子來(lái)通過(guò)代碼來(lái)解釋下如何實(shí)現(xiàn)一個(gè)事件模式的。
具體代碼為:
using System;
using System.Threading;
namespace BirthdayEventDemo
{
class Program
{
static void Main(string[] args)
{
// 實(shí)例化一個(gè)事件源對(duì)象
Me eventSource = new Me("Learning Hard");
// 實(shí)例化關(guān)注事件的對(duì)象
Friend1 obj1 = new Friend1();
Friend2 obj2 = new Friend2();
// 使用委托把對(duì)象及其方法注冊(cè)到事件中
eventSource.BirthDayEvent+=new BirthDayEventHandle(obj1.SendGift);
eventSource.BirthDayEvent+=new BirthDayEventHandle(obj2.Buycake);
// 事件到了觸發(fā)生日事件,事件的調(diào)用
eventSource.TimeUp();
Console.Read();
}
}
// 第一步: 定義一個(gè)類(lèi)型用來(lái)保存所有需要發(fā)送給事件接收者的附加信息
public class BirthdayEventArgs : EventArgs
{
// 表示過(guò)生日人的姓名
private readonly string name;
public string Name
{
get { return name;}
}
public BirthdayEventArgs(string name)
{
this.name = name;
}
}
// 第二步:定義一個(gè)生日事件,首先需要定義一個(gè)委托類(lèi)型,用于指定事件觸發(fā)時(shí)被調(diào)用的方法類(lèi)型
public delegate void BirthDayEventHandle(object sender, BirthdayEventArgs e);
// 定義事件成員
public class Subject
{
// 定義生日事件
public event BirthDayEventHandle BirthDayEvent;
// 第三步:定義一個(gè)負(fù)責(zé)引發(fā)事件的方法,它通知已關(guān)注的對(duì)象(通知我的好友)
protected virtual void Notify(BirthdayEventArgs e)
{
// 出于線程安全的考慮,現(xiàn)在將對(duì)委托字段的引用復(fù)制到一個(gè)臨時(shí)字段中
BirthDayEventHandle temp = Interlocked.CompareExchange(ref BirthDayEvent, null, null);
if (temp != null)
{
// 觸發(fā)事件,與方法的使用方式相同
// 事件通知委托對(duì)象,委托對(duì)象調(diào)用封裝的方法
temp(this, e);
}
}
}
// 定義觸發(fā)事件的對(duì)象,事件源
public class Me : Subject
{
private string name;
public Me(string name)
{
this.name = name;
}
public void TimeUp()
{
BirthdayEventArgs eventarg = new BirthdayEventArgs(name);
// 生日到了,通知朋友們
this.Notify(eventarg);
}
}
// 好友對(duì)象
public class Friend1
{
public void SendGift(object sender,BirthdayEventArgs e)
{
Console.WriteLine(e.Name+" 生日到了,我要送禮物");
}
}
public class Friend2
{
public void Buycake(object sender, BirthdayEventArgs e)
{
Console.WriteLine(e.Name + " 生日到了,我要準(zhǔn)備買(mǎi)蛋糕");
}
}
}
運(yùn)行結(jié)果為:

三、編譯器是如何解釋事件的呢?
上面我們已經(jīng)介紹了如何去實(shí)現(xiàn)自己去實(shí)現(xiàn)一個(gè)事件模式的,大家可以展開(kāi)代碼來(lái)具體的查看的,實(shí)現(xiàn)過(guò)程主要是——定義觸發(fā)對(duì)象的事件源(指的是誰(shuí)過(guò)生日)->定義關(guān)注你生日事件的朋友對(duì)象-> 方法登記對(duì)事件的關(guān)注,當(dāng)事件觸發(fā)時(shí)通知登記的方法被調(diào)用。然而相信大家還有有疑問(wèn)——到底C#中的事件是什么呢?編譯器又是如何去解釋它的?下面就為大家解除下疑惑的:
首先事件其實(shí)就是委托的(確切的說(shuō)事件就是委托鏈),從上面的代碼中,我們定義的事件除了使用event關(guān)鍵字外,還用到了一個(gè)委托類(lèi)型,然而委托是一個(gè)類(lèi),類(lèi)肯定就有屬性字段的,然而我們就可以把事件理解為委托的一個(gè)屬性,屬性的返回值是一個(gè)委托類(lèi)型。說(shuō)事件是委托的一個(gè)屬性,是有根據(jù)的,我們通過(guò)中間語(yǔ)言代碼可以知道編譯器是如何去解釋我們定義的事件的。
// 第二步:定義一個(gè)生日事件,首先需要定義一個(gè)委托類(lèi)型,用于指定事件觸發(fā)時(shí)被調(diào)用的方法類(lèi)型
public delegate void BirthDayEventHandle(object sender, BirthdayEventArgs e);
// 定義生日事件
public event BirthDayEventHandle BirthDayEvent;
當(dāng)我們像上面定義一個(gè)事件時(shí),編譯器會(huì)把它轉(zhuǎn)換為3段代碼(大家可以通過(guò)IL反匯編程序來(lái)查看的):
// 1. 一個(gè)被初始化為null的私有委托字段
private BirthDayEventHandle BirthDayEvent =null;
//2. 一個(gè)公共add_BirthDayEvent方法
public void add_BirthDayEvent(BirthDayEventHandle value)
{
// 以一種線程安全的方式從事件中添加一個(gè)委托
}
// 3. 一個(gè)公共的remove_BirthDayEvent方法
public void remove_BirthDayEvent(BirthDayEventHandle value)
{
// 以一種線程安全的方式從事件中移除一個(gè)委托
}
第一段代碼一個(gè)委托的私有字段,該字段是對(duì)一個(gè)委托列表的頭部的引用,事件發(fā)生時(shí)會(huì)通知這個(gè)列表中的委托。字段初始化為null,表明無(wú)關(guān)注人登記了對(duì)事件的關(guān)注。
第二段代碼是一個(gè)以add為前綴的方法,該方法是由編譯器自動(dòng)命名的,代碼內(nèi)容調(diào)用Delegate.Combine方法將委托實(shí)例添加到委托列表中,返回新的列表地址,并將這個(gè)地址存回字段。
第三段代碼也是一個(gè)方法,它使得一個(gè)對(duì)象注銷(xiāo)對(duì)事件的關(guān)注,同樣的方法體調(diào)用Delegate.Remove方法將委托實(shí)例從委托列表中刪除,返回新的列表地址,并將這個(gè)地址存回字段中。(注,如果試圖刪除一個(gè)從未添加過(guò)的方法,Delegate.Remove方法在內(nèi)部將不做任何事情,也就是說(shuō),不會(huì)拋出任何一次,也不會(huì)顯示任何警告,事件的方法集合保持不變)。
同時(shí)大家也可以通過(guò)調(diào)試來(lái)說(shuō)明事件是一個(gè)委托鏈的,大家可以在 eventSource.BirthDayEvent+=new BirthDayEventHandle(obj2.Buycake);這行代碼設(shè)置一個(gè)斷點(diǎn)調(diào)試的,下面是我調(diào)試過(guò)程中的一個(gè)截圖,大家也可以自己調(diào)試看看的,這樣將會(huì)更加理解事件是一個(gè)委托鏈的概念:

按F10運(yùn)行一行后的截圖

通過(guò)上面的截圖,相信大家對(duì)于事件是一個(gè)委托鏈的概念相信會(huì)有進(jìn)一步的理解的。
四、小結(jié)
到這里本專(zhuān)題的內(nèi)容也就介紹完了, 希望通過(guò)本專(zhuān)題,大家可以對(duì)事件有進(jìn)一步的理解,理解事件與委托之間的關(guān)系。這個(gè)專(zhuān)題通過(guò)自己實(shí)現(xiàn)的一個(gè)事件模式里解釋事件的本質(zhì),然而我們經(jīng)常使用的是Net類(lèi)庫(kù)中定義好的事件,然而有些剛接觸C#的人卻不理解Net中定義的事件背后所做的事情,只是知道點(diǎn)下按鈕后在Click方法里面寫(xiě)入自己的一些控制代碼,然而背后的過(guò)程具體是怎樣的,既然事件是委托,那么Click事件是委托類(lèi)型,其中的委托類(lèi)型又是怎么被實(shí)例化的呢?這些內(nèi)容將在下一個(gè)專(zhuān)題給大家分享下的。
以上就是簡(jiǎn)單聊聊c# 事件的詳細(xì)內(nèi)容,更多關(guān)于c# 事件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#實(shí)現(xiàn)對(duì)二維數(shù)組排序的方法
這篇文章主要介紹了C#實(shí)現(xiàn)對(duì)二維數(shù)組排序的方法,實(shí)例分析了C#數(shù)組遍歷與排序的相關(guān)技巧,需要的朋友可以參考下2015-06-06
C#采用Winform實(shí)現(xiàn)類(lèi)似Android的Listener
這篇文章主要介紹了C#采用Winform實(shí)現(xiàn)類(lèi)似Android的Listener,很實(shí)用的技巧,需要的朋友可以參考下2014-08-08
C#實(shí)現(xiàn)SMTP服務(wù)發(fā)送郵件的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用C#實(shí)現(xiàn)SMTP服務(wù)發(fā)送郵件的功能,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)C#有一定的幫助,感興趣的小伙伴可以跟隨小編一起了解一下2022-12-12
C#中的隨機(jī)數(shù)函數(shù)Random()
這篇文章介紹了C#生成隨機(jī)數(shù)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05
C#讀取與寫(xiě)入txt文件內(nèi)容的實(shí)現(xiàn)方法
在 C# 中讀取和寫(xiě)入文本文件內(nèi)容是一個(gè)常見(jiàn)的任務(wù),本文主要介紹了使用幾種不同方法讀取和寫(xiě)入文本文件的示例,并通過(guò)代碼示例介紹的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2024-08-08
Unity實(shí)現(xiàn)已知落點(diǎn)和速度自動(dòng)計(jì)算發(fā)射角度
這篇文章主要為大家詳細(xì)介紹了Unity實(shí)現(xiàn)已知落點(diǎn)和速度自動(dòng)計(jì)算發(fā)射角度,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-02-02

