欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解C#編程中.NET的弱事件模式

 更新時(shí)間:2015年07月13日 09:29:27   投稿:goldensun  
這篇文章主要介紹了C#編程中.NET的弱事件模式,深入討論了C#中相關(guān)的垃圾回收機(jī)制,需要的朋友可以參考下

 引言

你可能知道,事件處理是內(nèi)存泄漏的一個(gè)常見(jiàn)來(lái)源,它由不再使用的對(duì)象存留產(chǎn)生,你也許認(rèn)為它們應(yīng)該已經(jīng)被回收了,但不是,并有充分的理由。

在這個(gè)短文中(期望如此),我會(huì)在 .Net 框架的上下文事件處理中展示這個(gè)問(wèn)題,之后我會(huì)教你這個(gè)問(wèn)題的標(biāo)準(zhǔn)解決方案,弱事件模式。有兩種方法,即:

  •     “傳統(tǒng)”方法 (嗯,在 .Net 4.5 前,所以也沒(méi)那么老),它實(shí)現(xiàn)起來(lái)比較繁瑣
  •     .Net 4.5 框架提供的新方法,它則是盡其可能的簡(jiǎn)單

(源代碼在 這里 可供使用。)

從常見(jiàn)事物開(kāi)始

在一頭扎進(jìn)本文核心內(nèi)容前,讓我們回顧一下在代碼中最常使用的兩個(gè)事物:類和方法。
事件源

讓我為您介紹一個(gè)基本但很有用的事件源類,它最低限度地揭示了足夠的復(fù)雜性來(lái)說(shuō)明這一點(diǎn):

 

public class EventSource
{
  public event EventHandlerEvent = delegate { };
 
  public void Raise()
  {
    Event(this, EventArgs.Empty);
  }
}

對(duì)好奇那個(gè)奇怪的空委托初始化方法(delegate { })的人來(lái)說(shuō),這是一個(gè)用來(lái)確保事件總被初始化的技巧,這樣就可以不必每次在使用它之前都要檢查它是否不為NULL。

觸發(fā)垃圾收集的實(shí)用方法

在.net中,垃圾收集以一種不確定的方式觸發(fā)。這對(duì)我們的實(shí)驗(yàn)很不利,我們的實(shí)驗(yàn)需要以一種確定的方式跟蹤對(duì)象的狀態(tài)。

所以,我們必須定期觸發(fā)自己的垃圾收集操作,同時(shí)避免復(fù)制管道代碼,管道代碼已經(jīng)在在一個(gè)特定的方法中釋放:
 

static void TriggerGC()
{
  Console.WriteLine("Starting GC.");
 
  GC.Collect();
  GC.WaitForPendingFinalizers();
  GC.Collect();
 
  Console.WriteLine("GC finished.");
}

雖然不是很復(fù)雜,但是如果你不是很熟悉這種模式,還是有必要小小解釋一下:

  •     第一個(gè) GC.Collect() 觸發(fā).net的CLR垃圾收集器,對(duì)于負(fù)責(zé)清理不再使用的對(duì)象,和那些類中沒(méi)有終結(jié)器(即c#中的析構(gòu)函數(shù))的對(duì)象,CLR垃圾收集器足夠勝任
  •     GC.WaitForPendingFinalizers() 等待其他對(duì)象的終結(jié)器執(zhí)行;我們需要這樣做,因?yàn)椋銓⒖吹轿覀兪褂媒K結(jié)器方法去追蹤我們的對(duì)象在什么時(shí)候被收集的
  •     第二個(gè)GC.Collect() 確保新生成的對(duì)象也被清理了

引入問(wèn)題

首先讓我們?cè)囍ㄟ^(guò)一些理論,最重要的是還有一個(gè)演示的幫助,去了解事件監(jiān)聽(tīng)器有哪些問(wèn)題。
背景

一個(gè)對(duì)象要想被作為事件偵聽(tīng)器,需要將其實(shí)例方法之一登記為另一個(gè)能夠產(chǎn)生事件的對(duì)象(即事件源)的事件處理程序,事件源必須保持一個(gè)到事件偵聽(tīng)器對(duì)象的引用,以便在事件發(fā)生時(shí)調(diào)用此偵聽(tīng)器的處理方法。

這很合理,但如果這個(gè)引用是一個(gè) 強(qiáng)引用,則偵聽(tīng)器會(huì)作為事件源的一個(gè)依賴 從而不能作為垃圾回收,即使引用它的最后一個(gè)對(duì)象是事件源。


下面詳細(xì)圖解在這下面發(fā)生了什么:

事件處理問(wèn)題

這將不是一個(gè)問(wèn)題,如果你可以控制listener object的生命周期,你可以取消對(duì)事件源的訂閱當(dāng)當(dāng)你不再需要listener,常??梢允褂胐isposable pattern(用后就扔的模式)。

但是如果你不能在listener生命周期內(nèi)驗(yàn)證單點(diǎn)響應(yīng),在確定性的方式中你不能把它處理掉,你必須依賴GC處理...這將從不會(huì)考慮你所準(zhǔn)備的對(duì)象,只要事件源還存在著!

例子

理論都是好的,但還是讓我們看看問(wèn)題和真正的代碼。

這是我們勇敢的時(shí)間監(jiān)聽(tīng)器,還有點(diǎn)幼稚,我們很快知道為什么:
 

public class NaiveEventListener
{
  private void OnEvent(object source, EventArgs args)
  {
    Console.WriteLine("EventListener received event.");
  }
 
  public NaiveEventListener(EventSource source)
  {
    source.Event += OnEvent;
  }
 
  ~NaiveEventListener()
  {
    Console.WriteLine("NaiveEventListener finalized.");
  }
}

用一個(gè)簡(jiǎn)單例子來(lái)看看怎么實(shí)現(xiàn)運(yùn)作:
 

Console.WriteLine("=== Naive listener (bad) ===");
 
EventSource source = new EventSource();
 
NaiveEventListener listener = new NaiveEventListener(source);
 
source.Raise();
 
Console.WriteLine("Setting listener to null.");
listener = null;
 
TriggerGC();
 
source.Raise();
 
Console.WriteLine("Setting source to null.");
source = null;
 
TriggerGC();

輸出:

 

EventListener received event.
Setting listener to null.
Starting GC.
GC finished.
EventListener received event.
Setting source to null.
Starting GC.
NaiveEventListener finalized.
GC finished.

讓我們分析下這個(gè)運(yùn)作流程:

  •     “EventListener received event.“:這是我們調(diào)用 “source.Raise()”的結(jié)果; perfect, seems like we're listening.
  •     “Setting listener to null.“: 我們把本地事件監(jiān)聽(tīng)器對(duì)象引用賦空值,這樣應(yīng)該可以讓垃圾回收器回收了.
  •     “Starting GC.“: 垃圾回收開(kāi)始.
  •     “GC finished.“: 垃圾回收開(kāi)始, 但是 但是我們的事件監(jiān)聽(tīng)器沒(méi)有被回收器回收, 這樣就證明了事件監(jiān)聽(tīng)器的析構(gòu)函數(shù)沒(méi)有被調(diào)用。
  •     “EventListener received event.“: 第二次調(diào)用 “source.Raise()”來(lái)確認(rèn),發(fā)現(xiàn)這監(jiān)聽(tīng)器還活著。
  •     “Setting source to null.“: 我們?cè)谫x空值給事件的原對(duì)象.
  •     “Starting GC.“: 第二次垃圾回收.
  •     “NaiveEventListener finalized.“: 這一次幼稚的事件監(jiān)聽(tīng)終于被回收了,遲到總好過(guò)沒(méi)有.
  •     “GC finished.“:第二次垃圾回收完成.


結(jié)論:確實(shí)有一個(gè)隱藏的對(duì)事件監(jiān)聽(tīng)器的強(qiáng)引用,目的是防止它在事件源被回收之前被回收!

希望有針對(duì)此問(wèn)題的標(biāo)準(zhǔn)解決方案:讓事件源可以通過(guò)弱引用來(lái)引用偵聽(tīng)器,在事件源存在時(shí)也可以回收偵聽(tīng)器對(duì)象。

這里有一個(gè)標(biāo)準(zhǔn)的模式及其在.NET框架上的實(shí)現(xiàn):弱事件模式(http://msdn.microsoft.com/en-us/library/aa970850.aspx)。 And there is a standard pattern and its implementation in the .Net framework: the weak event pattern.

弱事件模式

讓我們看看在.NET中如何應(yīng)付這個(gè)問(wèn)題,

通常有超過(guò)一種方法去做,但是在這種情況下可以直接決定:

  •     如果你正在使用 .Net 4.5 ,那么你將從簡(jiǎn)單的實(shí)現(xiàn)受益
  •     另外,你必須依靠一點(diǎn)人為的技巧手段

傳統(tǒng)方式

  •     WeakEventManager 是所有模式管道的封裝
  •     IWeakEventListener 是管道,它允許一個(gè)組件連接到WeakEventManager管件

(這兩個(gè)位于WindowBase程序集,你將需要參考你自己的如果你不在開(kāi)發(fā)WPF項(xiàng)目,你應(yīng)該準(zhǔn)確的參考WindowBase)


因此這有兩步處理.

首先通過(guò)繼承WeakEventManager來(lái)實(shí)現(xiàn)一個(gè)自定義事件管理器:

  •     重寫(xiě) StartListening 和 StopListening 方法,分別注冊(cè)一個(gè)新的handler和注銷一個(gè)已存在的; 它們將被WeakEventManager基類使用。
  •     提供兩個(gè)方法來(lái)訪問(wèn)listener列表, 命名為 “AddListener” 和 “RemoveListener “,給自定義事件管理器的使用者使用。
  •     通過(guò)在自定義事件管理器上暴露一個(gè)靜態(tài)屬性,提供一個(gè)方式去獲得當(dāng)前線程的事件管理器。
  • 之后使listenr實(shí)現(xiàn)IWeakEventListenr接口:
  •     實(shí)現(xiàn) ReceiveWeakEvent 方法
  •     嘗試去處理這個(gè)事件
  •     如果無(wú)誤的處理好事件,將返回true


有很多要說(shuō)的,但是可以相對(duì)地轉(zhuǎn)換成一些代碼:

首先是自定義弱事件管理器:
 

public class EventManager : WeakEventManager
{
  private static EventManager CurrentManager
  {
    get
    {
      EventManager manager = (EventManager)GetCurrentManager(typeof(EventManager));
 
      if (manager == null)
      {
        manager = new EventManager();
        SetCurrentManager(typeof(EventManager), manager);
      }
 
      return manager;
    }
  }
 
 
  public static void AddListener(EventSource source, IWeakEventListener listener)
  {
    CurrentManager.ProtectedAddListener(source, listener);
  }
 
  public static void RemoveListener(EventSource source, IWeakEventListener listener)
  {
    CurrentManager.ProtectedRemoveListener(source, listener);
  }
 
  protected override void StartListening(object source)
  {
    ((EventSource)source).Event += DeliverEvent;
  }
 
  protected override void StopListening(object source)
  {
    ((EventSource)source).Event -= DeliverEvent;
  }
}

之后是事件listener:
 

public class LegacyWeakEventListener : IWeakEventListener
{
  private void OnEvent(object source, EventArgs args)
  {
    Console.WriteLine("LegacyWeakEventListener received event.");
  }
 
  public LegacyWeakEventListener(EventSource source)
  {
    EventManager.AddListener(source, this);
  }
 
  public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
  {
    OnEvent(sender, e);
 
    return true;
  }
 
  ~LegacyWeakEventListener()
  {
    Console.WriteLine("LegacyWeakEventListener finalized.");
  }
}

檢查下:
 

Console.WriteLine("=== Legacy weak listener (better) ===");
 
EventSource source = new EventSource();
 
LegacyWeakEventListener listener = new LegacyWeakEventListener(source);
 
source.Raise();
 
Console.WriteLine("Setting listener to null.");
listener = null;
 
TriggerGC();
 
source.Raise();
 
Console.WriteLine("Setting source to null.");
source = null;
 
TriggerGC();

輸出:
 

LegacyWeakEventListener received event.
Setting listener to null.
Starting GC.
LegacyWeakEventListener finalized.
GC finished.
Setting source to null.
Starting GC.
GC finished.

非常好,它起作用了,我們的事件listener對(duì)象現(xiàn)在可以在第一次GC里正確的析構(gòu),即使事件源對(duì)象還存活,不再泄露內(nèi)存了.

但是要寫(xiě)一堆代碼就為了一個(gè)簡(jiǎn)單的listener,想象一下你有一堆這樣的listener,你必須要為每個(gè)類型的寫(xiě)一個(gè)弱事件管理器!

如果你很擅長(zhǎng)代碼重構(gòu),你可以發(fā)現(xiàn)一個(gè)聰明的方式去重構(gòu)所有通用的代碼.

在.Net 4.5 出現(xiàn)之前,你必須自己實(shí)現(xiàn)弱事件管理器,但是現(xiàn)在,.Net提供一個(gè)標(biāo)準(zhǔn)的解決方案來(lái)解決這個(gè)問(wèn)題了,現(xiàn)在就來(lái)回顧下吧!

 .Net 4.5 方式

.Net 4.5 已介紹了一個(gè)新的泛型版本的遺留WeakEventManager: WeakEventManager<TEventSource, TEventArgs>.

(這個(gè)類可以在WindowsBase集合.)

多虧了 .Net WeakEventManager<TEventSource, TEventArgs> 自己處理泛型, 不用去一個(gè)個(gè)實(shí)現(xiàn)新事件管理器.

而且代碼還簡(jiǎn)單和可讀:
 

public class WeakEventListener
{
  private void OnEvent(object source, EventArgs args)
  {
    Console.WriteLine("WeakEventListener received event.");
  }
 
  public WeakEventListener(EventSource source)
  {
    WeakEventManager.AddHandler(source, "Event", OnEvent);
  }
 
  ~WeakEventListener()
  {
    Console.WriteLine("WeakEventListener finalized.");
  }
}

簡(jiǎn)單的一行代碼,真簡(jiǎn)潔.

其他實(shí)現(xiàn)的使用也是相似的, 就是裝入所有東西到事件listener類里:

 

Console.WriteLine("=== .Net 4.5 weak listener (best) ===");
 
EventSource source = new EventSource();
 
WeakEventListener listener = new WeakEventListener(source);
 
source.Raise();
 
Console.WriteLine("Setting listener to null.");
listener = null;
 
TriggerGC();
 
source.Raise();
 
Console.WriteLine("Setting source to null.");
source = null;
 
TriggerGC();

輸出也是肯定正確的:
 

WeakEventListener received event.
Setting listener to null.
Starting GC.
WeakEventListener finalized.
GC finished.
Setting source to null.
Starting GC.
GC finished.

預(yù)期結(jié)果也跟之前一樣,還有什么問(wèn)題?!

結(jié)論

正如你看到的,在.Net上實(shí)現(xiàn)弱事件模式 是十分直接, 特別在 .Net 4.5.

如果你沒(méi)有用.Net 4.5來(lái)實(shí)現(xiàn),將需要一堆代碼, 你可能不去用任何模式而是直接使用C# (+= and -=), 看看是否有內(nèi)存問(wèn)題,如果注意到泄露,還需要花必要的時(shí)間去實(shí)現(xiàn)一個(gè)。

但是用 .Net 4.5, 它是自由和簡(jiǎn)潔,而且由框架管理, 你可以毫無(wú)顧慮的選擇它, 盡管沒(méi)有 C# 語(yǔ)法 “+=” 和 “-=” 的酷, 但是語(yǔ)義是清晰的,這才是最重要的.

相關(guān)文章

  • C#中Task.Yield的用途深入講解

    C#中Task.Yield的用途深入講解

    這篇文章主要給大家介紹了關(guān)于C#中Task.Yield的用途的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-11-11
  • 淺析WPF中Binding的數(shù)據(jù)校驗(yàn)和類型轉(zhuǎn)換

    淺析WPF中Binding的數(shù)據(jù)校驗(yàn)和類型轉(zhuǎn)換

    在WPF開(kāi)發(fā)中,Binding實(shí)現(xiàn)了數(shù)據(jù)在Source和Target之間的傳遞和流通,那在WPF開(kāi)發(fā)中,如何實(shí)現(xiàn)數(shù)據(jù)的校驗(yàn)和類型轉(zhuǎn)換呢,下面就跟隨小編一起學(xué)習(xí)一下吧
    2024-03-03
  • C# WPF上位機(jī)實(shí)現(xiàn)和下位機(jī)TCP通訊的方法

    C# WPF上位機(jī)實(shí)現(xiàn)和下位機(jī)TCP通訊的方法

    這篇文章主要介紹了C# WPF上位機(jī)實(shí)現(xiàn)和下位機(jī)TCP通訊的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-03-03
  • C#用Activex實(shí)現(xiàn)Web客戶端讀取RFID功能的代碼

    C#用Activex實(shí)現(xiàn)Web客戶端讀取RFID功能的代碼

    由于要在Web項(xiàng)目中采用RFID讀取功能,所以有必要開(kāi)發(fā)Activex,一般情況下開(kāi)發(fā)Activex都采用VC,VB等,但對(duì)這兩塊不是很熟悉,所以采用C#編寫(xiě)Activex的方式實(shí)現(xiàn)
    2011-05-05
  • 使用C#語(yǔ)言實(shí)現(xiàn)的查詢條件界面展開(kāi)和收起功能

    使用C#語(yǔ)言實(shí)現(xiàn)的查詢條件界面展開(kāi)和收起功能

    這篇文章主要介紹了使用C#語(yǔ)言實(shí)現(xiàn)的查詢條件界面展開(kāi)和收起功能的完美解決方案,需要的朋友可以參考下
    2016-11-11
  • webBrowser執(zhí)行js的方法,并返回值,c#后臺(tái)取值的實(shí)現(xiàn)

    webBrowser執(zhí)行js的方法,并返回值,c#后臺(tái)取值的實(shí)現(xiàn)

    下面小編就為大家?guī)?lái)一篇webBrowser執(zhí)行js的方法,并返回值,c#后臺(tái)取值的實(shí)現(xiàn)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-12-12
  • C# DataTable數(shù)據(jù)遍歷優(yōu)化詳解

    C# DataTable數(shù)據(jù)遍歷優(yōu)化詳解

    這篇文章主要介紹了C# DataTable數(shù)據(jù)遍歷優(yōu)化詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-01-01
  • C#紋理畫(huà)刷TextureBrush用法實(shí)例

    C#紋理畫(huà)刷TextureBrush用法實(shí)例

    這篇文章主要介紹了C#紋理畫(huà)刷TextureBrush用法,實(shí)例分析了紋理畫(huà)刷TextureBrush的具體使用技巧,需要的朋友可以參考下
    2015-06-06
  • C#中的委托介紹

    C#中的委托介紹

    這篇文章主要介紹了C#中的委托介紹,本文講解了聲明委托的方式、匿名方法、多播委托等內(nèi)容,需要的朋友可以參考下
    2015-01-01
  • C# 反射(Reflection)的用處分析

    C# 反射(Reflection)的用處分析

    反射(Reflection)是C#里很重要的一個(gè)特性,其它語(yǔ)言也有這個(gè)特性,比如JAVA。反射這個(gè)特性是很實(shí)用的,如果使用過(guò)struts, hibernate, spring等等這些框架的話,便會(huì)知道反射這個(gè)特性是多么的強(qiáng)大了。在我接觸過(guò)的那些框架中,沒(méi)有一個(gè)框架是不使用反射的。
    2015-03-03

最新評(píng)論