12

管理されていないリソースを破棄するための「標準の完全な」IDisposable実装に関する情報はたくさんありますが、実際には、このケースは (非常に) まれです (ほとんどのリソースは既に管理クラスによってラップされています)。この質問は、より一般的な「マネージド リソースのみ」の場合の IDisposable の最小限の実装に焦点を当てています。

IDisposable1:以下のコード の最小限の実装は正しいですか? 問題はありますか?

2:提示された最小限の 実装に完全な標準IDisposable実装 ( Dispose()Dispose(bool)など)を追加する理由はありますか?Finalizer

Dispose3: この最小限のケースで仮想化するのは賢明ですか? (提供していないためDispose(bool))?

4: この最小限の実装が、(この場合は役に立たない) ファイナライザーを含む完全な標準実装に置き換えられた場合、GC がオブジェクトを処理する方法が変わりますか? 欠点はありますか?

5: 例にはおよび イベント ハンドラーが含まれています。これらのケースは特に重要です。これらのケースを破棄しないと、GC がそれらを破棄するまで、Timerオブジェクトを生きたままにしておくことができます (thisの場合Timer、イベント ハンドラーの場合)。eventSource時間です。このような例は他にありますか?

class A : IDisposable {
    private Timer timer;
    public A(MyEventSource eventSource) {
        eventSource += Handler
    }

    private void Handler(object source, EventArgs args) { ... }

    public virtual void Dispose() {
        timer.Dispose();
        if (eventSource != null)
           eventSource -= Handler;
    }
}

class B : A, IDisposable {
    private TcpClient tpcClient;

    public override void Dispose() {
        (tcpClient as IDispose).Dispose();
        base.Dispose();
    }   
}

refs:
MSDN
SO: 管理対象リソースをいつ管理する必要がある
か SO: C# の Dispose() メソッドで管理対象リソースを破棄する方法
SO: 管理対象リソースをクリーンアップするための Dispose()

4

3 に答える 3

8
  1. 派生クラスがアンマネージ リソースを直接所有していなければ、実装は正しく、問題はありません。

  2. 完全なパターンを実装する正当な理由の 1 つは、「最小の驚きの原則」です。この単純なパターンを説明する信頼できるドキュメントが MSDN にないため、保守開発者は疑問を抱くかもしれません - StackOverflow に問い合わせる必要があると感じていたとしても :)

  3. はい、この場合、Dispose が仮想であっても問題ありません。

  4. Dispose が呼び出され、正しく実装されている (つまり、GC.SuppressFinalize を呼び出している) 場合、不要なファイナライザーのオーバーヘッドは無視できます。

IDisposable.NET Framework 自体の外部にある圧倒的多数のクラスは、マネージドリソースIDisposableを所有しているためです。IDisposableそれらがアンマネージ リソースを直接保持することはまれです。これは、P/Invoke を使用して、.NET Framework によって公開されていないアンマネージ リソースにアクセスする場合にのみ発生します。

したがって、この単純なパターンを促進するための適切な議論がおそらくあります。

  • アンマネージ リソースが使用されるまれなケースでは、ファイナライザー ( SafeHandleIDisposableなど) を実装するシールされたラッパー クラスでラップする必要があります。このクラスはシールされているため、完全な IDisposable パターンは必要ありません。

  • 他のすべてのケースでは、圧倒的多数の場合、より単純なパターンを使用できます。

しかし、Microsoft やその他の権威ある情報源が積極的に推奨しない限り、私は完全なIDisposableパターンを使い続けます。

于 2013-09-24T10:11:57.057 に答える
2

もう 1 つのオプションは、コードをリファクタリングして継承を回避し、IDisposableクラスを封印することです。次に、可能な継承をサポートするためのぎこちない回転が不要になるため、より単純なパターンを正当化するのは簡単です。個人的には、ほとんどの場合、このアプローチを採用しています。シールされていないクラスを使い捨てにしたいというまれなケースでは、「標準」パターンに従います。このアプローチを育むことの良い点の 1 つは、継承ではなく合成に向かわせる傾向があることです。これにより、通常、コードの保守とテストが容易になります。

于 2013-09-25T15:02:40.293 に答える
0

私が推奨するDisposeパターンは、非仮想Dispose実装が virtual にチェーンすることvoid Dispose(bool)です。できれば次のような後です。

int _disposed;
public bool Disposed { return _disposed != 0; }
void Dispose()
{
  if (System.Threading.Interlocked.Exchange(ref _disposed, 1) != 0)
    Dispose(true);
  GC.SuppressFinalize(this); // In case our object holds references to *managed* resources
}

このアプローチを使用するとDispose(bool)、複数のスレッドが同時に呼び出そうとしても、一度しか呼び出されないことが保証されます。このような同時破棄の試みはまれですが (*)、防御するのは安上がりです。基本クラスが上記のようなことをしない場合、すべての派生クラスには、独自の冗長な double-dispose ガード ロジックと、おそらく冗長なフラグも必要です。

(*) ほとんどがシングルスレッドでブロッキング I/O を使用する一部の通信クラスではDispose、任意のスレッド コンテキストから呼び出して、独自のスレッドをブロックしている I/O 操作をキャンセルできます [明らかにDispose、そのスレッドで呼び出すことはできません。そのスレッドは、ブロックされている間は何もできないため]。そのようなオブジェクト、またはそれらをカプセル化するオブジェクトがDispose、メインスレッドによって破棄されようとしている瞬間に現在の操作を中止する手段として、外部スレッドに試行させることは完全に可能であり、不合理ではありません。 . 同時呼び出しはまれである可能性がありますが、その可能性は、コードが一方の呼び出しで動作し、他方を無視できる場合Dispose、「設計上の問題」を示すものではありません。Dispose

于 2013-09-24T20:15:36.223 に答える