2016 年 11 月

第 31 卷,第 11 期

本文章是由機器翻譯。

.NET Framework - 隱藏的可處置項目

Artak Mkrtchyan | 2016 年 11 月

因為它們可讓您釋出資源既定的方式,很好用,可處置型別。不過,有具有可處置型別的開發人員甚至不知情就其中的情況。使用 creational 的設計模式是一個範例的位置使用的可處置型別可能不明顯的情況下,這將導致未處置的物件。本文將說明如何處理此問題。首先我要檢閱的一些 creational 設計模式。

Creational 設計模式

這些抽象遠離實際的實作,而 「 交談 」 中的介面語言 creational 設計模式的極大的好處。處理物件建立機制來建立適合方案的物件。相較於基本物件建立 creational 設計模式來改善物件建立程序的幾個層面。以下是兩個已知的優點 creational 設計模式︰

  • 抽象概念︰ 這些抽象物件型別建立,因此呼叫者並不知道實際物件所傳回的內容是 — 他們知道只有介面。
  • 建立內幕︰ 封裝建立特定型別執行個體的相關知識。

接下來,我會提供兩種知名 creational 設計模式的簡短概觀。

Factory 方法設計模式Factory 方法設計模式是我最愛的其中一個,使用它許多在日常工作。此模式會使用 factory 方法來處理未指定建立物件的確切類別建立物件的問題。而不是直接呼叫類別建構函式,您必須呼叫 factory 方法來建立物件。Factory 方法傳回的子類別會實作抽象 (介面或基底類別)。[圖 1 示範這種模式的統一模組化語言 (UML) 圖表。

[圖 1, ,ConcreteProduct 是特定類型的 IProduct 抽象/介面。同樣地,ConcreteCreator 是 ICreator 介面的特定實作。

Factory 方法設計模式
[圖 1 Factory 方法設計模式

此模式的用戶端會使用 ICreator 執行個體,並將會呼叫其建立方法,來取得新的執行個體的 IProduct,而不需要知道已傳回的實際的產品。

抽象 Factory 設計模式 抽象 Factory 設計模式的目標是提供介面來建立相關或相依物件的系列,但未指定的具象實作。

這是讓用戶端要求的 factory 物件,建立所需的抽象型別的物件,並傳回至用戶端物件的抽象的指標 shelters 用戶端程式碼,從建立物件的麻煩。特別是,這表示用戶端程式碼並不會知道有關具象型別。它只會處理抽象型別。

加入新的具象型別支援是由建立新的處理站型別和修改要使用不同的處理站類型視用戶端程式碼來處理。在大部分情況下,這是變更一行程式碼。當用戶端程式碼不需要變更以容納新的處理站類型,這很明顯地簡化了處理變更。[圖 2 抽象 Factory 設計模式的 UML 圖表會顯示。

抽象 Factory 設計模式
[圖 2 抽象 Factory 設計模式

用戶端的觀點而言,是以下列程式碼片段來代表抽象的處理站的使用方式︰

IAbstractFactory factory = new ConcreteFactory1();
IProductA product = factory.CreateProductA();

用戶端就可以修改實際的處理站的實作來控制在幕後建立的產品類型,並將具有絕對不會影響在程式碼。

此程式碼是只是範例。在正確的結構化的程式碼,處理站執行個體化本身會可能會抽 — 以做為範例的 factory 方法模式。

問題

在這兩個設計模式範例,時發生 factory 相關。處理站是實際方法/程序,傳回透過抽象的建構型別參考中的用戶端呼叫的回應。

技術上來說,您可以使用 factory 來建立物件,只要有一個抽象概念,如所示 [圖 3

簡單的抽象概念和其使用方式的範例
圖 3 簡易的抽象概念和其使用方式的範例

處理站會處理不同可用的實作,根據相關因素之間的抉擇。

根據相依性反轉原則中︰

  • 高層級的模組不應該相依於低分層模組。兩者都應該相依於抽象概念。
  • 抽象概念不應該相依於詳細資料。詳細資料應該相依於抽象概念。

這就技術上來說,表示每個層級的相依性鏈結,相依性應該取代的抽象概念。此外,可以建立這些抽象概念,而且在許多情況下應該 — 透過 factory 來處理。

所有這強調重要的處理站在日常的撰寫程式碼。不過,它們實際上隱藏的問題︰ 可處置型別。這些詳細資料之前,我將先討論 IDisposable 介面和處置設計模式。

處置設計模式

所有的程式在執行期間取得資源,例如記憶體、 檔案控制代碼和資料庫連接。開發人員必須小心使用這類資源,因為它們已取得並使用後,就必須釋出資源。

Common Language Runtime (CLR) 支援透過記憶體回收行程 (GC) 的自動記憶體管理。您沒有清除 Managed 記憶體明確,因為 GC,會自動執行。不幸的是,有其他類型的資源 (稱為 Unmanaged 資源),仍然需要明確釋放。GC 不被設計來處理這些類型的資源,所以開發人員必須負責發行的。

不過,CLR 會協助開發人員處理 unmanaged 資源。System.Object 類型定義的公用虛擬方法,呼叫 Finalize,GC 會在回收物件的記憶體之前會呼叫。Finalize 方法通常稱為完成項。您可以覆寫方法,清除其他物件所使用的 unmanaged 資源。

這項機制,不過,有一些缺點,因為 GC 執行的某些方面。

GC 會偵測到的物件可供回收時,會呼叫完成項。這會發生在物件不需要再尚無法確定期間。

當 GC 必須呼叫完成項時,它會有延遲記憶體回收的下一回合的實際記憶體集合。這會延後的記憶體物件的集合,甚至更久。這是 System.IDisposable 介面會傳入。Microsoft.NET Framework 提供您需要為開發人員提供一種機制,手動釋放 unmanaged 的資源實作 IDisposable 介面。實作此介面的型別稱為可處置型別。IDisposable 介面會定義單一的無參數方法呼叫 Dispose。立即釋放物件不需要為它所參考的任何 unmanaged 的資源,應該呼叫 dispose。

您可能會問 「 為什麼應該呼叫 Dispose 自己時,GC 會最終處理它幫我知道? 」 答案需要有個別的文章,內容也觸及 GC 的執行對效能的影響層面。這是超出本文的範圍,因此我會說明。

目前沒有決定是否應該可處置型別時要遵循特定規則。法則是︰ 如果指定類型的物件會參考 unmanaged 的資源或其他可處置的物件,則它也應該可處置。

處置模式會定義特定的實作 IDisposable 介面。它需要實作的兩個 Dispose 方法︰ 不需要參數 (IDisposable 介面所定義) 和另一個受保護的一個公用虛擬單一布林值參數。很明顯地,如果型別要密封,受保護虛擬應取代私用。

[圖 4 實作 Dispose 設計模式

public class DisposableType : IDisposable {
  ~DisposableType() {
    this.Dispose(false);
  }
  public void Dispose() {
    this.Dispose(true);
    GC.SuppressFinalize(this);
  }
  protected virtual void Dispose(bool disposing) {
    if (disposing) {
      // Dispose of all the managed resources here
    }
    // Dispose of all the unmanaged resources here
  }
}

布林值參數指出呼叫 dispose 方法的方式。公用方法會呼叫受保護的其中一個參數具有值"true"。 同樣地,您應該呼叫基底類別階層架構中變方法的多載。Dispose(true)。

Dispose 模式實作也需要 Finalize 方法多載。這是為了涵蓋其中開發人員忘記呼叫 Dispose 方法,當物件不需要再的案例。GC 會被呼叫的完成項,因為參考的 managed 的資源可能已經 (或將) 會清除,因此變方法呼叫完成項完成時,您應該處理 unmanaged 資源的釋放。

切換回主要的主題,問題派上用場時處理 creational 設計模式搭配使用時可處置的物件。

設想一個具象類型實作抽象方法,也會實作 IDisposable 介面。假設它是在我的範例,ConcreteImplementation2 中所示 [圖 5

使用 IDisposable 實作的抽象概念
[圖 5 使用 IDisposable 實作的抽象概念

請注意,IAbstraction 介面本身不會繼承自 IDisposable。

現在看看用戶端程式碼中,抽象概念要使用。IAbstraction 介面沒有變更,因為用戶端不會在意少了在幕後任何可能的變更。當然,用戶端不會猜測,他已被授與的物件,其中他現在是負責處置。但事實上是 IDisposable 執行個體不真的有預期,而且在許多情況下,這些物件永遠不會取得明確處置用戶端程式碼。

希望是 ConcreateImplementation2 的實際實作實作處置設計模式,這不一定如此。

現在很明顯,最簡單的機制來處理傳回的 IAbstraction 執行個體也實作 IDisposable 介面的案例就是建立一項明確檢查用戶端程式碼,如下所示︰

IAbstraction abstraction = factory.Create();
try {
  // Operations with abstraction go here
}
finally {
  if (abstraction is IDisposable)
    (abstraction as IDisposable).Dispose();
}

不過,很快就會變成冗長乏味的程序。

不幸的,使用區塊不能與 IAbstraciton,因為它不會明確地擴充 IDisposable。讓我想到了協助程式類別,以包裝在 finally 區塊,讓邏輯使用 using 區塊,以及。[圖 6 顯示完整的程式碼的類別,並提供範例使用方式。

[圖 6 PotentialDisposable 類型和其使用方式

public sealed class PotentialDisposable<T> : IDisposable where T : class {
  private readonly T instance;
  public T Instance { get { return this.instance; } }
  public PotentialDisposable(T instance) {
    if (instance == null) {
      throw new ArgumentNullException("instance");
    }
    this.instance = instance;
  }
  public void Dispose() {
    IDisposable disposableInstance = this.Instance as IDisposable;
    if (disposableInstance != null) {
      disposableInstance.Dispose();
    }
  }
}
The client code:
IAbstraction abstraction = factory.Create();
using (PotentialDisposable<IAbstraction> wrappedInstance =
  new PotentialDisposable<IAbstraction>(abstraction)) {
    // Operations with abstraction wrapedInstance.Instance go here
}

如您所見的 「 用戶端程式碼 」 部分 [圖 6, ,使用 PotentialDisposable < T > 類別減少到只需要幾行,有使用的用戶端程式碼區塊。

您可以說您可以只更新 IAbstraction 介面,讓它 IDisposable。這可能是比較好的解決方案,在某些情況下,而非其他電腦。

在您擁有 IAbstraction 介面和延伸 IDisposable IAbstraction 的合理的情況,您應該做的。實際上,一個很好的例子就是 System.IO.Stream 抽象類別。類別實際實作 IDisposable 介面,但它有沒有定義的實際邏輯。原因是類別的寫入器知道大部分的子類別會有某種類型的可處置的成員。

另一種情況︰ 當您擁有 IAbstraction 介面,但它沒有任何意義來延伸 IDisposable 時,大部分的實作,它不是可處置。思考 ICustomCollection 介面做為範例。您有數種記憶體中實作,而且突然您需要新增一些資料庫基礎的實作,而這將是只可處置實作。

最後這種情況就是當您沒有擁有 IAbstraction 介面,所以您不需要其控制權。請考慮 ICollection,由資料庫支援的範例。

總結

的抽象概念,就是透過 factory 方法時,務必記住 disposables 撰寫用戶端程式碼時。使用這個簡單的 helper 類別是一種方式以確保您的程式碼要盡可能有效率處理可處置的物件時。


Artak Mkrtchyan 是住在美國華盛頓州 Redmond 的資深軟體工程師 他愛撰寫程式碼,就像他愛釣魚。

感謝下列 Microsoft 技術專家來檢閱這份文件︰ Paul Brambilla
Paul Brambilla 是專精於雲端服務和基本基礎結構的 Microsoft 的資深軟體開發人員。