事件,定義了事件成員的類型允許類型或類型的實例通知其它對象發生了特定的事情。
按照我自己的理解而言,事件可以被(方法)關注,也可以被(方法)取消關注,事件發生後關注了事件的一方會了解到,並對事件做出相應的應對(執行方法)。(我每次都是這麼理解的,這樣從字面意義上更好理解一點)
眾所周知,事件實際上就是基於委托的。而委托是調用回調函數的一種類型安全的方式。
今天寫一個關於分手的事件Demo,算是生動形象吧。
定義事件參數類(可忽略這步)
一個事件發生後若要傳遞附加的參數信息,就需要定義事件參數類,需要繼承EventArgs,否則就直接使用EventArgs.Empty即可。(EventArgs.Empty實際上就是new EventArgs())
public class 分手EventArgs : EventArgs { public 分手EventArgs(string name, string title) { this._分手的人 = name; this._分手原因 = title; } public string 分手的人 { get { return _分手的人; } } public string 分手原因 { get { return _分手原因; } } private readonly string _分手的人; private readonly string _分手原因; }
定義事件成員
public class Troy { //委托類型EventHandler<T>的聲明public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e); //第一個對象顧名思義是事件發出者,這裡肯定就是Troy發生了事件 //第二個參數傳遞的就是我們之前定義的事件附加信息 public event EventHandler<分手EventArgs> 宣稱要分手; }
現在有了一個Troy的宣稱分手事件。
既然事件有了,那麼接下來就是去讓本人去引發這個事件
引發事件
於是就變成了這樣
public class Troy { //委托類型EventHandler<T>的聲明public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e); //第一個對象顧名思義是事件發出者,這裡肯定就是Troy發生了事件 //第二個參數傳遞的就是我們之前定義的事件附加信息 public event EventHandler<分手EventArgs> 宣稱要分手; //定義負責引發事件的方法來通知已關注事件的人,一般是要定義為protected和virtual protected virtual void On宣稱要分手(分手EventArgs e) { //如果有人聽我說這個事,那麼就說,沒人聽我就肯定不說了,你在程序裡玩自言自語也不是不行,不過顯得很傻而已(完美貼切好形象) if(宣稱要分手!=null)this.宣稱要分手(this,e); } }
本書中其實還介紹了一個出於線程安全考慮的引發事件的寫法,
因為如果在我宣稱要分手前,本來聽我講這個事的人A有另一個人B叫他,A他突然跑掉了(在另一個線程中 宣稱要分手 的委托鏈就被移除了委托),那麼此時 宣稱要分手 這個事件就沒人聽了(宣稱要分手為null),然後我醞釀了半天的話吐不出來就報了個Null異常。
所以有了下面這種寫法
protected virtual void On宣稱要分手(分手EventArgs e) { //下面代碼的意思就是:我要說分手的時候將關注了 宣稱要分手 這個事件的人都拉到討論組中 //然後就算被另一個線程的人將在我旁邊的人都叫走了,實際上因為我把他們丟拉到討論組中了 //此時討論組中都有成員,所以關注的人還是獲悉了這個悲傷的故事 var 討論組 = System.Threading.Volatile.Read(ref 宣稱要分手);//Volatile.Read僅僅起到賦值宣稱要分手的引用給討論組,之所以不用等於,是因為可能會被編譯器優化時去掉臨時變量 討論組。 if (討論組 != null)this.宣稱要分手(this,e); }
然而JIT編譯器表示,實際上這種用等於也可以,只是為了防范於未然。
然而此處我只想安靜地分手,所以讓我們沿用更上面的說法
真實的引發事件
我每天都可以說很多分手事件,有的是你,有的還是你。
為了更准確的把這個事給說清楚了,我也許還要傳遞個信息,就是這次可能要分手的是我。
public class Troy { //委托類型EventHandler<T>的聲明public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e); //第一個對象顧名思義是事件發出者,這裡肯定就是Troy發生了事件 //第二個參數傳遞的就是我們之前定義的事件附加信息 public event EventHandler<分手EventArgs> 宣稱要分手; //定義負責引發事件的方法來通知已關注事件的人,一般是要定義為protected和virtual,前者是要,後者是為了派生類的必要 protected virtual void On宣稱要分手(分手EventArgs e) { if(宣稱要分手!=null)this.宣稱要分手(this,e); } public void 宣稱自己要分手() { //這一步我們把事說清楚 var e = new 分手EventArgs("Troy", "心累"); On宣稱要分手(e);//如果子類沒有重寫這個事件引發函數,那麼就告訴所有關注的人這個事 } }
事件類型被編譯後會出現什麼?
C#編譯器將事件編譯後會轉換成3個東西:一個私有的委托字段 宣稱要分手,一個公共的關注事件的方法 add_宣稱要分手,一個公共的取消關注的方法 remove_宣稱要分手。
除了這三個東西,編譯器還會在托管程序集的元數據中生成一個事件定義記錄項,它的作用只是為了建立“事件”的抽象概念和它的訪問器方法之間的聯系。(高深吧?然而你並不需要懂)
關於關注事件的那些人
事件以及事件的引發都弄好了,接下來就是定義關注事件的那些人了。以下人物由真實人物改編:
//下面是將婚同事李 public class Lee { public Lee(Troy troy) { //初始化就關注事件 troy.宣稱要分手 += this.AfterListen; } private void AfterListen(Object sender, 分手EventArgs e) { Console.WriteLine("讓troy去玩游戲"); } } //然後是單身同事肖 public class Xiao { public Xiao(Troy troy) { troy.宣稱要分手 += this.AfterListen; } private void AfterListen(Object sender, 分手EventArgs e) { Console.WriteLine("表示Troy打擊單身狗"); } //由於Xiao並不是每天都和Lee一樣,與Troy同行,所以Xiao也許開始聽得到,後來跑遠了,就取消關注事件了 private void UnListen(Troy troy) { troy.宣稱要分手 -= this.AfterListen; } }
一個對象只要某個方法關注了事件,那麼它就不能被回收了。所以如果你想讓垃圾處理器回收某對象,就讓他不要再關注事件了。
好吧,就這些了。
PS:
這確實是一個真實的故事,就在昨天。
我也不知道我為什麼還有心情寫博客,反正除了這個我也已經不知道該干嘛好了。
大概是因為就算失戀了也不可能說不去吃飯睡覺之類的感覺吧。
更加令人驚奇的是,效率不知道為什麼出奇得高,以至於十一點前就完成了學習和博客。
每個人都需要去成長,成長的故事大多都是不舒服的,一如熬夜學習寫博客,一如分手這件事。
懷著感恩的心去面對已經失去的人,反而比什麼樣的療傷都來得有效d(╯﹏╰)b