標籤:
沒有偉大的願望,就沒有偉大的天才--Aaronyang的部落格(www.ayjs.net)-www.8mi.me
1. 事件-我的講法
老師常告訴我,事件是特殊的委託,為委託提供了一種發布/訂閱機制。
- 自訂事件:自訂一個類,繼承EventArgs
- 使用泛型委派EventHandler<T>,本質:public delegate void EventHandler<TEventArgs>(object sender,TEventArgs e) where TEventHandlers:EventArgs
- 定義事件的簡單寫法 public event EventHandler<TEventArgs> myevent;這裡的EventHandler<TEventArgs>指的是符合2中的方法簽名形式,void,有兩個參數的,第一個sender參數包含事件的寄件者。第二個參數提供了事件的相關資訊。因為這個事件可以自訂,所以可以帶來很多豐富的資訊,也可以使用系統內建的。
- 使用事件屬性中的add和remove添加或者刪除委託的處理常式(事件),也可以自己使用+=或者-=處理
- 使用事件
1.1 我們來寫一個簡單的例子,讓你理解這幾個知識。
需求:我需要使用者使用我提供的類,可能就是個dll了,可以在自己的類中,定義事件的詳細內容。例如:部落格事件,有了1篇新文章,可能會有閱讀者去閱讀部落格,有的人使用電腦,有的人使用手機,有的人使用kindle
設計:
第一步:自訂事件,好處是可以在觸發事件時候提供其他的對象值
public class BlogEventArgs : EventArgs { /// <summary> /// 部落格標題 /// </summary> /// <param name="title"></param> public BlogEventArgs(string title) { this.Title = title; } //部落格標題 public string Title { get; set; } }
第二步:定義個事件,方便使用者增加 自己的定義的事件,刪除自己的定義的事件,一個調用使用者綁定好的事件(特殊的委託)的方法
public class BlogEventProvider { /// <summary> /// 第一步:定義個事件,並封裝,給使用者綁定這個事件 /// </summary> private event EventHandler<BlogEventArgs> blogEvents; public event EventHandler<BlogEventArgs> BlogEvents { add { blogEvents += value; } remove { blogEvents -= value; } } //觸發使用者綁定的事件,或者事件前加些處理,然後再觸發 public void PreviewReadBlog(string title) { Console.WriteLine("正在擷取部落格《{0}》的內容...",title); OnReadBlog(title); } protected virtual void OnReadBlog(string title) { if (blogEvents != null) { blogEvents(this, new BlogEventArgs(title)); } } }
第三步:定義個部落格支援的裝置枚舉
/// <summary> /// 部落格文章支援的裝置 /// </summary> public enum BlogSupportDevice { PC, Kindle, Mobile }
第四步:定義閱讀部落格的人,當然這裡的事件的實現,也可以寫在別處。
/// <summary> /// 部落格閱讀者 /// </summary> public class BlogReader { public BlogReader(string readername, BlogSupportDevice device) { this.ReaderName = readername; this.Device = device; } public string ReaderName { get; set; } public BlogSupportDevice Device { get; set; } /// <summary> /// 因為需要知道部落格的標題還有其他細節,所以自訂了一個BlogEventArgs類 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> public void OnReadBlog(object sender,BlogEventArgs e) { Console.WriteLine("{3}:{0}使用了{1}閱讀了《{2}》",ReaderName,Device.ToString(),e.Title,DateTime.Now.ToLocalTime()); } }
第五步:使用
第六步:拓展與反思
1. 如果我綁定了N次相同的事件,然後執行事件,是只會觸發1次,還是觸發N次?
答案:是N次,這也是上篇文章留的題目,委託被綁定多個相同的方法會執行多次嗎?
2. 完整第五步代碼,使用-=移除上一個事件,不然執行事件時候,會執行多次,因為事件是特殊的委託,具有多播委託的特性。
static void Main(string[] args) { //定義了事件提供者 BlogEventProvider provider = new BlogEventProvider(); //定義部落格閱讀人 BlogReader ay = new BlogReader("小aaronyang", BlogSupportDevice.PC); //ay開始綁定自己定義的事件,事件提供者,也在觸發這個事件之前加了一些措施,而不是任由使用者自己搞的 provider.BlogEvents += ay.OnReadBlog; //開始閱讀部落格 provider.PreviewReadBlog("[Aaronyang] 寫給自己的WPF4.5 筆記[2相依性屬性]"); provider.BlogEvents -= ay.OnReadBlog;//移除ay閱讀者 //定義部落格閱讀人 BlogReader yy = new BlogReader("楊洋", BlogSupportDevice.Kindle); //ay開始綁定自己定義的事件,事件提供者,也在觸發這個事件之前加了一些措施,而不是任由使用者自己搞的 provider.BlogEvents += yy.OnReadBlog; //開始閱讀 provider.PreviewReadBlog("[Aaronyang] 寫給自己的WPF4.5 筆記[1布局]"); Console.ReadLine(); }
:
2. 事件不複雜,就好比js中把方法當做一個參數傳遞,例如 function A(B,d){B()}
這裡B是一個方法,人家調用A時候就可以自訂一個B,而B只是一個function對象,具有幾個參數,在A函數的參數中未體現,但是在調用B時候就有體現了。假設B函數具有2個參數(x,y),那麼
function A(B,d){ var e=B(1,3)}
function cusB(x,y){return x*y}
A(cusB,‘自訂‘)
說白了,感覺有點像給使用者留事件介面
===============aaronyang========www.8mi.me================== 2.弱事件-引入
理解事件的發布和訂閱模型-publisher和 listener
aaronyang大話講解:publisher就是定義事件的地方,如上例就是BlogEventProvider類,它裡面定義了private event EventHandler<BlogEventArgs> blogEvents;
而Listener就是給這個事件 添加或者刪除具體事件實現的地方。如上例就是BlogReader類,它裡面有blogEvents符合事件(blogEvents)的方法簽名。因此就可以正常用+=和-=委託的東東了。
OK,下面我們來測試為什麼需要弱事件?對的,就是防止事件的記憶體泄露問題。
接下來,我們在Program類中加一個記憶體回收方法,方便測試
static void TriggerGC() { Console.WriteLine("Starting GC."); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); Console.WriteLine("GC finished."); }
第一個 GC.Collect() 觸發.net的CLR垃圾收集器,對於負責清理不再使用的對象,和那些類中沒有終結器(即c#中的解構函式)的對象,CLR垃圾收集器足夠勝任
GC.WaitForPendingFinalizers() 等待其他對象的終結器執行;
第二個GC.Collect() 確保新產生的對象也被清理了
接下來,我們接著上一個例子繼續寫,上面第五步的代碼假設你有了。
我們現在把ay對象置空,接著讓記憶體回收ay對象,然後我們執行事件
static void Main(string[] args) { //定義了事件提供者 BlogEventProvider provider = new BlogEventProvider(); //定義部落格閱讀人 BlogReader ay = new BlogReader("小aaronyang", BlogSupportDevice.PC); //ay開始綁定自己定義的事件,事件提供者,也在觸發這個事件之前加了一些措施,而不是任由使用者自己搞的 provider.BlogEvents += ay.OnReadBlog; //開始閱讀部落格 provider.PreviewReadBlog("[Aaronyang] 寫給自己的WPF4.5 筆記[2相依性屬性]"); //provider.BlogEvents -= ay.OnReadBlog;//移除ay閱讀者 ////定義部落格閱讀人 //BlogReader yy = new BlogReader("楊洋", BlogSupportDevice.Kindle); ////ay開始綁定自己定義的事件,事件提供者,也在觸發這個事件之前加了一些措施,而不是任由使用者自己搞的 //provider.BlogEvents += yy.OnReadBlog; ////開始閱讀 //provider.PreviewReadBlog("[Aaronyang] 寫給自己的WPF4.5 筆記[1布局]"); //測試事件的強引用 ay ay = null; TriggerGC(); provider.PreviewReadBlog("[Aaronyang] 寫給自己的WPF4.5 筆記[3進階事件]"); Console.ReadLine(); }
結果:
結果驚訝的發現:事件還能被執行!這就說明ay對象還在啊,這就是強引用,所以好多C#寫代碼都在執行完了事件後,就接著寫其他的代碼了,也不管記憶體回收能不能回收,導致了記憶體泄露的問題。
接著,我們再在 BlogEventProvider中增加一個解構函式,確認BlogEventProvider對象是否真的被回收
~BlogEventProvider(){ Console.WriteLine("BlogEventProvider的解構函式被執行"); }
OK,我們再在Program的Main方法中增加代碼:
provider = null; TriggerGC();
效果:
OK,我確定此時ay對象被釋放了。當然不一定要去釋放ay對象才能回收記憶體啊,事件的 記憶體泄露問題,你只要+=後,執行完,手動去-=事件就行了。這樣,記憶體回收行程在後台也就可以自動回收ay對象了,不然,你一直在使用provider對象時候,記憶體回收永遠都不能回收ay,假設provider中事件綁定了很多事件實現,而這些實現,你執行了,但是都沒有做-=,那麼就會導致能存泄露的問題,當然也就是記憶體高佔用的問題。好吧,假如你想再new一個provider,那個廢棄,那你new的太多的話,也會很多記憶體,而你如果沒有手動GC,那記憶體佔用更恐怖。
provider.BlogEvents -= ay.OnReadBlog; ay = null; TriggerGC(); provider.PreviewReadBlog("[Aaronyang] 寫給自己的WPF4.5 筆記[3進階事件]");
那麼這個問題,怎麼解決?.net4.5提供了一個更直接的方法,弱引用,當然4.5以前也有弱引用的其他方式,當然比較複雜,這裡不講了,只講4.5的方式。
3.弱事件-aaronyang開講
如果用4.5的泛型模式,則寫法超簡單,再也不用繼承WeakEventManager,IWeakEventListener了。MSDN位置:查看
而且再也不用擔心綁定多次同一個事件,被執行多次了
我們使用
System.Windows.WeakEventManager<BlogEventProvider, BlogEventArgs>.AddHandler(provider, "BlogEvents", ay.OnReadBlog);
替代剛才的
provider.BlogEvents += ay.OnReadBlog;
前提:你要引入WindowsBase.dll
static void Main(string[] args) { //定義了事件提供者 BlogEventProvider provider = new BlogEventProvider(); //定義部落格閱讀人 BlogReader ay = new BlogReader("小aaronyang", BlogSupportDevice.PC); //ay開始綁定自己定義的事件,事件提供者,也在觸發這個事件之前加了一些措施,而不是任由使用者自己搞的 //provider.BlogEvents += ay.OnReadBlog; System.Windows.WeakEventManager<BlogEventProvider, BlogEventArgs>.AddHandler(provider, "BlogEvents", ay.OnReadBlog); //測試綁定多次,也只會執行一次 //System.Windows.WeakEventManager<BlogEventProvider, BlogEventArgs>.AddHandler(provider, "BlogEvents", ay.OnReadBlog); //開始閱讀部落格 provider.PreviewReadBlog("[Aaronyang] 寫給自己的WPF4.5 筆記[2相依性屬性]"); ay = null; TriggerGC(); provider.PreviewReadBlog("[Aaronyang] 寫給自己的WPF4.5 筆記[3進階事件]"); Console.ReadLine(); }
效果:
發現第二次執行事件時候,ay已經被釋放了。真TM爽!!
======安徽六安=========www.ayjs.net==========aaronyang========楊洋========www.8mi.me==========
[AaronYang]C#人愛學不學8[事件和.net4.5的弱事件深入淺出]