0.簡述
C#中的事件可以說是應用相當多的一種機制,這里從淺至深了解下C#中的事件機制:
- 事件的簡單應用
- 自定義事件
- C#事件機制
- 使用事件不得不注意的事
PS:公司上網機上沒有IDE工具,所以,有些代碼只能是簡單寫一下,無法給出運行截圖和完整測測試項目了;大家見諒!
1.事件的簡單應用
在VS的IDE中最簡單的事件應用莫過於如下:
-
創建一個WINFORM項目
-
從工具欄中拖一個"按鈕"控件到form1界面上
-
雙擊按鈕控件,VS會自動創建按鈕單擊事件回調函數,並將函數與按鈕單擊事件關聯,並跳轉到事件回調函數button1_Click的代碼編輯界面。
-
在button1_Click函數中,填寫以下代碼:
MessageBox.Show("Hello,這是事件回調結果!");
-
按F5看看運行效果吧,點擊按鈕,彈出什么了?對的,是一個對話框:
Hello,這是事件回調結果!
這是最簡單的事件應用。接下來讓我們看看在代碼中如何應用事件:
- 還是剛才那個按鈕,讓我們按F7切換到代碼編輯界面吧。
- 在Form1的構造函數中,InitializeComponent();這一句下面添加我們的事件代碼,使得整個構造函數是下面的樣子:
public Form1() { InitializeComponent(); button1.MouseHove+=new EventHandler(Button1_MouseHover); }
- 哦,感謝微軟的工程師們,在編寫上面代碼過程中,IDE會自動生成最終的事件回調函數,我們只需要填寫函數內容即可:
private void Button1_MouseHover(object sender,EventArgs e) { MessageBox.Show("嘿,你怎么還不點?"); }
- OK,按F5看看運行效果吧(把鼠標放到按鈕上等一會)。
2.自定義事件
如何在自定義的類中定義事件呢?參看下面的代碼:
public class UserEventClass { public void TriggerEvent() { OnEventTrigger(); } protected virtual void OnEventTrigger() { var handler= EventTriggered; if(handler!=null) { handler(this,new EventArgs()); } } public event EventHandler EventTriggered; }
PS:上面的代碼效果是什么呢?調用UserEventClass 實例的TriggerEvent方法即會觸發該實例的EventTriggered事件。至於為什么使用OnEventTrigger來最終完成事件的觸發,會在下面做出解釋。
然后就可以象使用控件中的事件一樣來使用我們自定義的事件了。
當然,更復雜的自定義事件方式可以在博客園找到很多資料,基本上都大同小異,例如:
在上面的文章2中有講解如何安全引發事件,這就是上面自定義事件代碼中使用OnEventTrigger來最終完成事件的觸發的原因。同時,OnEventTrigger被標為protected virtual,這樣在UserEventClass的子類中,可以重寫OnEventTrigger方法來進行需要的修改。
3.C#事件機制
所有的教材里面都會先講解C#委托,然后才會講解C#事件,因為事件本質上仍然是由委托完成的,其神奇的表現僅僅是微軟工程師們給我們的語法糖(PS:可能有點偏激)。委托與事件之間的關聯可以參看下面的文章:
這里面要深究的其實應該是多播委托的委托鏈調用機制,拿出我們的反編譯神器Reflecter,看看Combine里面有什么?
- Delegate.Combine里面調用了參數1的Delegate. CombineImpl方法,這一方法在普通的Delegate類中會拋出異常,而MulticastDelegate類重寫了該方法。對該方法進行分析可以得出:將本實例及傳入的實例這兩個MulticastDelegate中的_invocationList列表組合,並以組合出的新列表創建一個新的MulticastDelegate返回。因此Combine返回的將是一個新的MulticastDelegate。_invocationList是一個List<Object>,這一點稍微有點奇怪,而且MulticastDelegate還提供了一個GetInvocationList方法返回Delegate數組,感覺有點多次一舉,還得裝箱拆箱。
- EventHandler繼承自MulticastDelegate,並實現了Invoke,BeginInvoke等方法,但是很遺憾,方法內容都是CLR內部調用了,無法探究,但是從MulticastDelegate的構成大略可以猜測出,是遍歷_invocationList,並逐個調用每個實例的Delegate.Invoke方法。
4.使用事件不得不注意的事
上面粗略描述了事件的使用,自定義及其深入機制,下面說說使用事件時需要注意的幾個問題:
- 都碰到過”線程間操作無效”的異常吧,沒錯,即便在事件中改變界面控件的屬性,調用方法等,也會碰到這個提示,但是有人會說,我在按鈕事件中去改變lable的值,為什么沒有異常呢?OK,還是委托的機制,委托其實是方法指針,調用委托就意味着委托的實際方法體運行在調用方一致的線程中,UI控件運行在什么線程下呢?界面線程,所以UI事件間進行操作是不會報異常的。如果你用一個SOCKET,如TCPClient,響應接受到數據的事件的話,那么這個事件將不會運行在界面主線程下,於是你就會看到”線程間操作無效”。
- 如果事件回調中有一個長時間執行的代碼會如何呢?在按鈕事件回調里寫過數據庫操作的人都有同感:卡……。這意味着事件是同步回調而不是異步回調,所以,把你的代碼移開吧,並且仔細檢查你的代碼,保證回調函數中沒有諸如死循環之類的……
- 如果關聯了多個事件回調,那么他們的執行有順序么?按照官方說法:不保證回調依照綁定順序調用。不過實際經驗來說大多數是和綁定順序相同的,同時,一個阻塞了,后面的都不會執行;一個回調異常將影響所有回調。
