C# Notizen 6 事件及其處理

一、理解事件
事件采用發(fā)布/訂閱模型欲逃,其中發(fā)行者決定在什么情況下引發(fā)事件,而訂戶決定為響應(yīng)事件而執(zhí)行的操作饼暑。事件可以有多個(gè)訂戶稳析,在這種情況下,將在事件被引發(fā)時(shí)同步調(diào)用事件處理程序弓叛。如果沒有訂戶彰居,事件將不會(huì)被引發(fā)。訂戶可處理來自多個(gè)發(fā)行者的多個(gè)事件撰筷。
ps:委托(delegate)
在傳統(tǒng)編程術(shù)語中陈惰,事件是一種回調(diào),能夠?qū)⒎椒ㄗ鳛閰?shù)傳遞給另一個(gè)方法毕籽。在 C#中抬闯,這些回調(diào)被稱為委托。事件處理程序不過是通過委托調(diào)用的方法关筒。
委托類似于C和C++中的函數(shù)指針以及Delphi封裝(closure)溶握,是一種類型安全的回調(diào)編寫方法。委托在調(diào)用方(而不是聲明方)的安全權(quán)限下運(yùn)行蒸播。
委托類型定義了一個(gè)方法簽名睡榆,可將任何具有兼容簽名的方法與委托相關(guān)聯(lián)。委托簽名和常規(guī)方法簽名之間的一個(gè)不同之處是袍榆,前者包含返回類型胀屿,可在參數(shù)列表中使用修飾符params。

二包雀、訂閱和取消訂閱
要響應(yīng)另一個(gè)類發(fā)布的事件宿崭,可訂閱它:定義一個(gè)事件處理程序,其簽名與事件的委托簽名匹配才写;然后使用加法賦值運(yùn)算符(+ =)將事件處理程序與事件相關(guān)聯(lián)葡兑。如下代碼演示了如何訂閱一個(gè)基于委托ElapseEventHandler的事件奴愉。

var timer = new Timer(1000);
timer.Elapsed +=new ElapsedEventHandler(TimerElapsedHandler);
void TimerElapsedHandler(object sender,ElapsedEventArgs e)
{    
    MessageBox.Show("The timer has expired.");
}

第一個(gè)參數(shù)總是名為 sender,其類型為 object铁孵,它表示引發(fā)事件的對(duì)象锭硼;第二個(gè)參數(shù)是傳遞給事件處理程序的數(shù)據(jù),名為e蜕劝,其類型為EventArgs或從EventArgs派生而來的類型檀头。事件處理程序的返回類型總是為void。

ps:方法組推斷
上述程序演示了關(guān)聯(lián)事件處理程序的傳統(tǒng)方法岖沛,但 C#允許使用一種更簡單的語法暑始,這被稱為方法組推斷(method group inference)。下面使用方法組推斷語法重寫了上述程序:

var timer = new Timer(1000);
timer.Elapsed += TimerElaspsedHandler;
void TimerElapsedHandler(object sender,ElapsedEventArgs e)
{    
    MessageBox.Show("The timer has expired.");
}

雖然Visual Studio自動(dòng)使用傳統(tǒng)語法來關(guān)聯(lián)事件處理程序婴削,但是當(dāng)關(guān)聯(lián)自己的事件處理程序時(shí)廊镜,通常使用方法組推斷語法。

可以任何方式給事件處理程序命名唉俗,但是為了確保一致性嗤朴,最好通過組合如下部分來生成名稱:提供事件的對(duì)象名、要處理的事件的名稱以及字樣Handler虫溜。

雖然很多類都使用這種方式來訂閱事件雹姊,但是Visual Studio使得訂閱事件很容易,尤其是訂閱用戶界面控件發(fā)布的事件時(shí)衡楞。
當(dāng)你不希望事件被引發(fā)時(shí)調(diào)用相應(yīng)的事件處理程序吱雏,必須取消訂閱該事件。另外瘾境,訂戶對(duì)象刪除前歧杏,必須取消訂閱事件;否則迷守,發(fā)行者將繼續(xù)保存一個(gè)引用犬绒,它指向表示訂戶事件處理程序的委托,這將禁止垃圾收集器釋放訂戶對(duì)象盒犹。
要取消訂閱事件懂更,可刪除XAML標(biāo)記中相應(yīng)的屬性或使用減法賦值運(yùn)算符(? =)眨业,如下所示急膀。
timer.Elapsed -= TimerElapsedHandler;

ps:匿名方法
匿名方法讓你能夠編寫一個(gè)未命名的內(nèi)聯(lián)語句塊,并在調(diào)用委托時(shí)執(zhí)行它龄捡。

如下代碼使用的是匿名方法卓嫂,而不是命名委托:

var timer = new Timer(1000);
timer.Elapsed += delegate(object sender,ElapsedEventArgs e)
{    
    MessageBox.Show("The timer has expired.");
}

雖然將匿名方法用作事件處理程序帶來了很多方便之處,但是取消訂閱事件時(shí)將不那么容易

三聘殖、發(fā)布事件
類和結(jié)構(gòu)都可發(fā)布事件(雖然事件通常用于類中)晨雳,為此只需使用簡單的事件聲明行瑞。事件可基于任何有效的委托類型,但是標(biāo)準(zhǔn)做法是讓事件基于委托 EventHandler 和EventHandler<T>餐禁。這些委托是在.NET Framework中預(yù)定義的血久,專門用于定義事件。
定義自己的事件時(shí)帮非,要做出的第一個(gè)決策是氧吐,是否要將自定義數(shù)據(jù)發(fā)送給事件。.NET Framework 提供了 EventArgs 類末盔,預(yù)定義的事件委托類型都支持它筑舅。如果要向事件發(fā)送自定義數(shù)據(jù),需要從EventArgs派生出一個(gè)新類陨舱;否則翠拣,可直接使用EventArgs類型,但以后就不能修改它了游盲,否則將破壞兼容性误墓。因此,總是應(yīng)該創(chuàng)建一個(gè)從EventArgs派生而來的新類(哪怕這個(gè)類最初為空)益缎,以便以后能夠靈活地添加數(shù)據(jù)优烧。
如下代碼是一個(gè)從EventArgs派生而來的類

public class CustomEventArgs:System.EventArgs
{
    private object data;    
    public CustomEventArgs(object data)    
    {        
        this.data = data;   
     }    
    public Object Data    
    {       
        get       
        {            
            return this.data;        
        }    
    }
}

聲明事件時(shí),通常使用類似于字段的語法链峭。如果沒有從 EvnetArgs 派生出新類畦娄,就使用委托類型EventHandler,如下所示

public class Contact
{    
    public event EventHandler AddresChanged;
}

如果從 EventArgs 派生出了新類弊仪,就需要使用泛型委托 EventHandler<T>熙卡,并用從EventArgs派生而來的類替換T。
雖然通常使用類似于字段的事件定義励饵,但是它并非總是效率最高的驳癌,尤其是當(dāng)類包含大量事件時(shí)。類包含大量事件時(shí)役听,通常只有其中的幾個(gè)事件有訂戶颓鲜。使用字段聲明語法時(shí),每個(gè)事件都需單獨(dú)聲明典予,這帶來了大量不必要的開銷甜滨。
為解決這種問題,C#還允許使用屬性語法定義事件瘤袖,如下所示

public class Contact
{    
    private EventHandlerList events = new EventHandlerList();    
    private static readonly object addressChangedEventKey = new object();    
    public event EventHandler AddressChanged    
    {       
         add       
         {            
             this.events.AddHandler(addressChangedEventKey, value);
         }       
         remove        
         {            
             this.events.RemoveHandler(addressChangedEventKey, value);        
         }    
    }
}

第3行聲明了一個(gè)EventHandlerList變量衣摩,這種類型專門設(shè)計(jì)用于存儲(chǔ)事件委托列表,這樣對(duì)于所有有訂戶的事件捂敌,都可在一個(gè)變量中存儲(chǔ)其對(duì)應(yīng)的列表項(xiàng)艾扮。接下來既琴,第4行聲明了一個(gè)只讀的靜態(tài)object變量,該變量名為addressChangedEventKey泡嘴,它是在EventHandlerList中用于表示事件的鍵甫恩。最后,第6~16行聲明了實(shí)際的事件酌予。
訪問器add將委托實(shí)例加入列表填物,而remove將其刪除。這兩個(gè)訪問器都使用預(yù)定義的鍵來添加和刪除實(shí)例霎终。
對(duì)于事件滞磺,一種方便而一致的描述方法是,將其分為事前事件和事后事件莱褒。

事后事件最常見击困,它在對(duì)象的狀態(tài)發(fā)生變化后發(fā)生。事前事件也稱為可撤銷的事件广凸,它在對(duì)象狀態(tài)發(fā)生變化前發(fā)生阅茶,讓你能夠撤銷事件;這些事件使用 CancelEventArgs 類來存儲(chǔ)事件數(shù)據(jù)谅海,這個(gè)類添加了一個(gè)Cancel屬性脸哀,可在代碼中讀寫它。創(chuàng)建自己的可撤銷事件時(shí)扭吁,應(yīng)從CancelEventArgs類派生自定義的事件數(shù)據(jù)類撞蜂。

四、引發(fā)事件
如果沒有發(fā)起事件的機(jī)制侥袜,定義事件就沒有多大意義蝌诡。發(fā)起事件也稱為引發(fā)或觸發(fā)事件,它遵循一種標(biāo)準(zhǔn)模式枫吧。通過遵循模式浦旱,使用事件將更容易,因?yàn)橄嚓P(guān)的結(jié)構(gòu)定義明確且一致九杂。
如下演示了完整的事件處理程序

public class Contact
{    
    public event EventHandler<AddressChangedEventArgs> AddressChanged;    
    private string address;

    protected virtual void OnAddressChaged(AddressChangeEventArgs e)    
    {        
        EventHandler<AddressChangedEventArgs> handler = AddressChanged;        
        if (handler != null)         
        {            
            handler (this, e);        
        }    
    }    
    public string Address    
    {        
        get 
        { 
            return this.address;    
        }        
        set         
        {            
            this.address = value;            
            AddressChangedEventArgs args = new AddressChangedEventArgs (this.address);
            OnAddressChaged (args);        
        }    
    }
}

第3行使用委托EventHandler<T>聲明了事件颁湖。第7~14行聲明了一個(gè)受保護(hù)的虛方法,用于引發(fā)該事件例隆。通過將這個(gè)方法聲明為protected和virtual甥捺,讓派生類能夠通過重寫這個(gè)方法(而不是訂閱事件)來處理這個(gè)事件;對(duì)派生類來說裳擎,這是一種更方便涎永、更自然的機(jī)制思币。最后鹿响,第22~23行創(chuàng)建了一個(gè)新的AddressChangedEventArgs實(shí)例并引發(fā)了事件羡微。如果事件沒有自定義數(shù)據(jù),就可以使用EventArgs.Empty字段表示空EventArgs惶我。
ps:引發(fā)使用屬性語法定義的事件
如果事件是使用屬性語法定義的妈倔,那么在用于引發(fā)事件的方法中,需要以稍微不同的方式從句柄列表中獲取事件句柄绸贡,如下所示盯蝴。

protected virtual void OnAddressChaged(AddressChangeEventArgs e)    
{        
    var handler = events [addressChangedEventKey] as EventHandler<AddressChangedEventArgs>;        
    if (handler != null)         
    {            
        handler (this, e);        
    }
}

根據(jù)約定,給引發(fā)事件的方法命名時(shí)听怕,以 On 打頭捧挺,然后是事件的名稱。對(duì)于非密封類的非靜態(tài)事件尿瞭,事件引發(fā)方法應(yīng)聲明為protected和virtual闽烙。對(duì)于靜態(tài)事件、密封類的非靜態(tài)事件以及結(jié)構(gòu)的事件声搁,事件引發(fā)方法應(yīng)聲明為公有的黑竞。事件引發(fā)方法的返回類型總是void,且只接收一個(gè)參數(shù)疏旨,該參數(shù)名為e很魂,其類型為EventArg或其合適的派生類。
這個(gè)方法遵循一種標(biāo)準(zhǔn)模式檐涝,即創(chuàng)建事件的一個(gè)臨時(shí)備份(第 9 行)遏匆,以免出現(xiàn)競(jìng)態(tài)條件(race condition):在 null檢查(第 10行)和引發(fā)事件(第 12行)之間,最后一個(gè)訂戶取消訂閱谁榜。
多線程和事件
這種模式只能避免一種可能的競(jìng)態(tài)條件—事件在檢查后變成了null拉岁;僅當(dāng)代碼是多線程時(shí),遵循這種模式才顯得重要惰爬。編寫多線程事件時(shí)喊暖,必須應(yīng)對(duì)眾多復(fù)雜的情形。例如撕瞧,在執(zhí)行依賴于某種狀態(tài)的代碼前陵叽,必須確保以線程安全的方式提供了這種狀態(tài)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末丛版,一起剝皮案震驚了整個(gè)濱河市巩掺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌页畦,老刑警劉巖胖替,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡独令,警方通過查閱死者的電腦和手機(jī)端朵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來燃箭,“玉大人冲呢,你說我怎么就攤上這事≌欣辏” “怎么了敬拓?”我有些...
    開封第一講書人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長裙戏。 經(jīng)常有香客問我乘凸,道長,這世上最難降的妖魔是什么累榜? 我笑而不...
    開封第一講書人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任翰意,我火速辦了婚禮,結(jié)果婚禮上信柿,老公的妹妹穿的比我還像新娘冀偶。我一直安慰自己,他們只是感情好渔嚷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開白布进鸠。 她就那樣靜靜地躺著,像睡著了一般形病。 火紅的嫁衣襯著肌膚如雪客年。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評(píng)論 1 305
  • 那天漠吻,我揣著相機(jī)與錄音量瓜,去河邊找鬼。 笑死途乃,一個(gè)胖子當(dāng)著我的面吹牛绍傲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播耍共,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼烫饼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了试读?” 一聲冷哼從身側(cè)響起杠纵,我...
    開封第一講書人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎钩骇,沒想到半個(gè)月后比藻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铝量,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年银亲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了慢叨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡群凶,死狀恐怖插爹,靈堂內(nèi)的尸體忽然破棺而出哄辣,到底是詐尸還是另有隱情请梢,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布力穗,位于F島的核電站毅弧,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏当窗。R本人自食惡果不足惜够坐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望崖面。 院中可真熱鬧元咙,春花似錦、人聲如沸巫员。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽简识。三九已至赶掖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間七扰,已是汗流浹背奢赂。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留颈走,地道東北人膳灶。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像立由,于是被迫代替她去往敵國和親袖瞻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理拆吆,服務(wù)發(fā)現(xiàn)聋迎,斷路器,智...
    卡卡羅2017閱讀 134,664評(píng)論 18 139
  • 一 關(guān)于委托 1.委托的概念: C# 中的委托(Delegate)是一種引用類型變量,它類似于C的函數(shù)指針,...
    SharaYuki閱讀 3,590評(píng)論 1 9
  • 事件 事件含義 事件由對(duì)象引發(fā)枣耀,通過我們提供的代碼來處理霉晕。一個(gè)事件我們必須訂閱(Subscribe)他們庭再,訂閱一個(gè)...
    天堂邁舞閱讀 2,973評(píng)論 1 7
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy閱讀 9,519評(píng)論 1 51
  • 城市堆肥,紙板箱是非常好的褐色材料牺堰。 最后一張4周左右拄轻,今天翻堆,基本已經(jīng)都變泥土了伟葫。 (這一箱剛好是夏季高溫恨搓,腐...
    艾小農(nóng)閱讀 1,862評(píng)論 0 0