2

OK、IDisposable に関するベスト プラクティスについていくつか読んだことがありますが、基本的に (ようやく) アイデアが浮かびました。

私の質問は、IDisposable 基本クラスからの継承に関するものです。私が見るすべての例は、サブクラスで同じコードブロックを何度も書いていますが、利点がわかりません。

仮想メソッドを基本クラスに単純に焼き付けて、(プライベートに実装された) IDisposable ルーチン内から適切なタイミングで呼び出して、サブクラスが混乱しないようにするだけでなく、リソースを管理する機会を得ることもできません。 ?

私の提案した基本クラス:

public abstract class DreamDisposableBase : IDisposable
{
    private bool _disposed = false;


    protected virtual void LocalDispose(bool disposing)
    {   
    }

    ~DreamDisposableBase()
    { 
        // finalizer being called implies two things:
        //  1. our dispose wasn't called (because we suppress it therein)
        //  2. we don't need to worry about managed resources; they're also subject to finalization

        // so....we need to call dispose with false, meaning dispose but only worry about *unmanaged* resources:

        dispose(false);
    }


    void IDisposable.Dispose()
    {
        dispose(true);  // true argument really just means that we're invoking it explicitly
    }


    private void dispose(bool disposing)
    {
        if (!_disposed)
        {
            // give sub-classes their chance to release their resources synchronously
            LocalDispose(disposing);

            if (disposing)
            { 
                // true path is our cue to release our private heap variables...
            }

            // do stuff outside of the conditional path which *always* needs to be done - release  unmanaged resources

            // tell .net framework we're done, don't bother with our finalizer - 
            GC.SuppressFinalize(this);

            // don't come back through here
            _disposed = true;
        }
    }

}
4

3 に答える 3

0

あなたは言う:

私の質問は、IDisposable 基本クラスからの継承に関するものです。私が見るすべての例は、サブクラスで同じコードブロックを何度も書いていますが、利点がわかりません。

これは正しくありません。IDisposable パターンのメソッドは次のとおりです。

protected virtual void Dispose(bool disposing)

クラスを継承してオーバーライドする必要があります。

Dispose(bool disposing)メソッドは実際にはあなた のメソッドであることに注意する必要がありますLocalDisposing(bool disposing)。そして、この事実があなたの混乱の原因だと思います。

影響力のある書籍の関連セクションをお読みください: フレームワーク設計ガイドライン、第 2 版

本を引用:

パターンを既に実装しているクラスから継承している場合は、単に Dispose(bool) メソッドをオーバーライドして、追加のリソース クリーンアップ ロジックを提供します。

派生クラスでは、コードは次のようになります。

protected override void Dispose(bool disposing)
{
  if(disposing)
  {
      //release own resources
  } 
}

また、この場合GC.SuppressFinalize(this)、非仮想の Dispose メソッドでのみ呼び出す必要があることに注意してください。また、コードでは IDisposable インターフェイスを暗黙的に実装しています。これに注意することが重要です。

于 2013-10-18T13:06:26.070 に答える
0

コードに実際の改善は見られませんが、パターンが非標準であるため、他の開発者が理解するのが難しい場合があります. パターンを使用して派生クラスを作成するには、次のようなものが必要です。

class DerivedDreamDisposable : DreamDisposableBase
{
    protected override void LocalDispose(bool disposing)
    {
        if (disposing)
        {
            // Dispose aggregated objects that are disposable.
        }

        // Dispose unmanaged resources.

        _disposed = true;
        base.LocalDispose(disposing);
    }
}

標準IDisposableパターンを使用すると、派生クラスは次のようになります。

class DerivedDisposable : DisposableBase
{
    bool _disposed;

    protected override void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                // Dispose aggregated objects that are disposable.
            }

            // Dispose unmanaged resources.

            _disposed = true;
        }
        base.Dispose(disposing);
    }
}

派生することによりDreamDisposable、オブジェクトの破棄された状態を追跡するためにフィールドを複製することを回避します。ただし、それ以外の方法は事実上同じです。また、基本クラスLocalDisposeは空で、コードをプライベートDisposeメソッドに移動しましたが、小さなリファクタリングを行うことで修正できます。

ただし、多くのクラスには解放するアンマネージ リソースがありません。また、Disposeメソッドの呼び出しは冪等であるため (複数回呼び出すことができます)、多くの場合、破棄された状態を追跡する必要はなく、破棄可能なコードは次のように単純化されます。

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        _child1.Dispose();
        _child2.Dispose();
    }
}

継承階層に管理されていないリソースがない場合、ファイナライザーは必要なく、disposingパラメーターは常に true になります。あなたのDispose方法は次のようになります。

protected override void Dispose(bool disposing)
{
    _child1.Dispose();
    _child2.Dispose();
}

一般に、ファイナライザーを回避するとパフォーマンスが向上します。これがGC.SuppressFinalize、ファイナライザーを実装して行った害を元に戻すために呼び出す理由です。

ObjectDisposedExceptionしかし、多くの場合、オブジェクトが破棄された後にメソッドが呼び出された場合はスローする必要があるため、オブジェクトの破棄された状態を追跡する必要があります。その場合、単純化されたDisposedメソッドは単純すぎます。以下は、各サブクラスでフラグを複製せずに_disposed、標準の破棄パターンを引き続き使用する方法の例です。

class DisposableBase : IDisposable
{
    bool _disposed;

    ~DisposableBase()
    {
        Dispose(false);
        GC.SuppressFinalize(this);
    }

    public void Dispose()
    {
        if (_disposed)
           return;
        Dispose(true);
        _disposed = true;
    }

    public void DoStuff()
    {
        ThrowIfDisposed();
        // Now, do stuff ...
    }

    protected virtual void Dispose(bool disposing)
    {
        // Dispose resources ...
    }

    protected void ThrowIfDisposed()
    {
        if (_disposed)
            throw new ObjectDisposedException(GetType().FullName);
    }
}

派生クラスは、破棄された状態を追跡する必要はなく、代わりに、ThrowIfDisposed破棄されていないオブジェクトに依存するすべてのパブリック メソッドで使用する必要があります。

于 2013-10-18T13:22:48.510 に答える
0

すべてのタイプにファイナライザーを持たせたくありません。ファイナライザーで作業を実行する必要があることは非常にまれです。disposing が true の場合、ほとんどすべての実装は何もしません。ファイナライザーは、Gen2 への昇格を引き起こし、2 つのコレクションをクリーンアップする必要があり、ファイナライザーの呼び出しはシングルスレッドであるため、パフォーマンスが低下します。

ほとんどのクラスはアンマネージ リソースをラップしません。ラップする場合は、そのSafeHandle型などを使用する必要があります。これにより、ファイナライザーも不要になります。

于 2013-10-18T13:44:20.917 に答える