web-dev-qa-db-ja.com

どのようにしてIDisposableがすべてのクラスに広がるのを防ぎますか?

これらの単純なクラスから始めます...

次のような単純なクラスのセットがあるとします。

_class Bus
{
    Driver busDriver = new Driver();
}

class Driver
{
    Shoe[] shoes = { new Shoe(), new Shoe() };
}

class Shoe
{
    Shoelace lace = new Shoelace();
}

class Shoelace
{
    bool tied = false;
}
_

BusにはDriverがあり、Driverには2つのShoesがあり、各ShoeにはShoelaceがあります。すべて非常にばかげています。

IDisposableオブジェクトをShoelaceに追加する

後で、Shoelaceの一部の操作をマルチスレッド化できると判断したため、スレッドが通信するためのEventWaitHandleを追加します。したがって、Shoelaceは次のようになります。

_class Shoelace
{
    private AutoResetEvent waitHandle = new AutoResetEvent(false);
    bool tied = false;
    // ... other stuff ..
}
_

IDisposableをShoelaceに実装する

しかし、現在 MicrosoftのFxCop は文句を言うでしょう: "次のIDisposable型のメンバーを作成するため、 'Shoelace'にIDisposableを実装します: 'EventWaitHandle'。"

さて、IDisposableShoelaceを実装すると、きちんとした小さなクラスがこの恐ろしい混乱になります。

_class Shoelace : IDisposable
{
    private AutoResetEvent waitHandle = new AutoResetEvent(false);
    bool tied = false;
    private bool disposed = false;

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    ~Shoelace()
    {
        Dispose(false);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                if (waitHandle != null)
                {
                    waitHandle.Close();
                    waitHandle = null;
                }
            }
            // No unmanaged resources to release otherwise they'd go here.
        }
        disposed = true;
    }
}
_

または(コメント投稿者が指摘したように)Shoelace自体にはアンマネージリソースがないため、Dispose(bool)およびDestructorを必要とせずに、より簡単な破棄の実装を使用できます。

_class Shoelace : IDisposable
{
    private AutoResetEvent waitHandle = new AutoResetEvent(false);
    bool tied = false;

    public void Dispose()
    {
        if (waitHandle != null)
        {
            waitHandle.Close();
            waitHandle = null;
        }
        GC.SuppressFinalize(this);
    }
}
_

IDisposableが広がっていくのを恐怖で見る

正解です。しかし、FxCopはShoeShoelaceを作成することに不満を言うので、ShoeIDisposableでなければなりません。

そしてDriverShoeを作成するので、DriverIDisposableでなければなりません。そしてBusDriverを作成するので、BusIDisposableでなければなりません。

突然Shoelaceへの小さな変更が原因で多くの作業が発生し、上司はBusをチェックアウトしてShoelaceに変更する必要があるのか​​と不思議に思っています。

質問

このIDisposableの蔓延をどのように防止しながら、アンマネージオブジェクトが適切に破棄されるようにしますか?

137
GrahamS

IDisposableが拡散するのを実際に「防ぐ」ことはできません。 AutoResetEventなどの一部のクラスは破棄する必要があります。最も効率的な方法は、ファイナライザのオーバーヘッドを回避するためにDispose()メソッドで破棄することです。ただし、このメソッドは何らかの方法で呼び出す必要があります。そのため、例のように、IDisposableをカプセル化または含むクラスはこれらを破棄する必要があるため、それらも破棄する必要があります。これを回避する唯一の方法は、次のとおりです。

  • 可能な場合はIDisposableクラスの使用を避け、単一の場所でイベントをロックまたは待機する、単一の場所で高価なリソースを保持する、など
  • 必要なときにだけ作成し、直後に破棄します(usingパターン)

IDisposableはオプションのケースをサポートしているため、無視できる場合があります。たとえば、WaitHandleはIDisposableを実装して、名前付きミューテックスをサポートします。名前が使用されていない場合、Disposeメソッドは何もしません。 MemoryStreamは別の例で、システムリソースを使用せず、そのDispose実装も何もしません。アンマネージリソースが使用されているかどうかを慎重に検討することは、教育に役立ちます。したがって、.netライブラリの利用可能なソースを調べたり、逆コンパイラを使用したりできます。

35
Grzenio

正確さの点では、親オブジェクトが使い捨てにする必要がある子オブジェクトを作成して本質的に所有している場合、オブジェクト関係を介したIDisposableの拡散を防ぐことはできません。この状況ではFxCopは正しく、親はIDisposableである必要があります。

オブジェクト階層のリーフクラスにIDisposableを追加しないようにすることができます。これは必ずしも簡単な作業ではありませんが、興味深い演習です。論理的な観点から、ShoeLaceが使い捨てである必要がある理由はありません。ここにWaitHandleを追加する代わりに、ShoeLaceとWaitHandleの間の関連付けを、それが使用される時点で追加することもできます。最も簡単な方法は、Dictionaryインスタンスを使用することです。

WaitHandleが実際に使用された時点で、マップを介してWaitHandleを緩い関連付けに移動できる場合は、このチェーンを解除できます。

19
JaredPar

IDisposableの拡散を防ぐには、1つのメソッド内での使い捨てオブジェクトの使用をカプセル化する必要があります。 Shoelaceのデザインを変えてみてください:

class Shoelace { 
  bool tied = false; 

  public void Tie() {

    using (var waitHandle = new AutoResetEvent(false)) {

      // you can even pass the disposable to other methods
      OtherMethod(waitHandle);

      // or hold it in a field (but FxCop will complain that your class is not disposable),
      // as long as you take control of its lifecycle
      _waitHandle = waitHandle;
      OtherMethodThatUsesTheWaitHandleFromTheField();

    } 

  }
} 

待機ハンドルのスコープはTiemethodに制限されており、クラスにディスポーザブルフィールドを含める必要がないため、クラス自体をディスポーザブルにする必要はありません。

待機ハンドルはShoelace内の実装の詳細であるため、宣言に新しいインターフェースを追加するなど、パブリックインターフェースを変更しないでください。使い捨てフィールドが不要になった場合はどうなりますか。IDisposable宣言を削除しますか? Shoelaceについて考えると 抽象化、あなたはそれがIDisposableのようなインフラストラクチャの依存関係によって汚染されるべきではないことをすぐに理解します。 IDisposableは、決定論的なクリーンアップを必要とするリソースを抽象化してカプセル化するクラス用に予約する必要があります。つまり、使い捨てがクラスの一部であるクラスの場合 抽象化

16
Jordão

これは、「クイックフィックス」が泥沼に発展する場合によくあるように、より高いレベルの設計問題のように感じられます。方法の詳細については、 this thread が役立つかもしれません。

3
Adam

デザインを非常に緊密に結合している場合、IDisposableが拡散するのを防ぐ技術的な方法はないと思います。次に、設計が正しいかどうか疑問に思う必要があります。

あなたの例では、靴に靴ひもを所有させることは理にかなっていると思います。おそらく、運転手は靴を所有する必要があります。ただし、バスはドライバーを所有するべきではありません。通常、バスの運転手はスクラップヤードまでバスを追いません:)運転手と靴の場合、運転手が自分の靴を作ることはめったにありません。

代替設計は次のとおりです。

class Bus
{
   IDriver busDriver = null;
   public void SetDriver(IDriver d) { busDriver = d; }
}

class Driver : IDriver
{
   IShoePair shoes = null;
   public void PutShoesOn(IShoePair p) { shoes = p; }
}

class ShoePairWithDisposableLaces : IShoePair, IDisposable
{
   Shoelace lace = new Shoelace();
}

class Shoelace : IDisposable
{
   ...
}

新しいデザインは、残念なことに、インスタンスを作成して靴とドライバーの具体的なインスタンスを破棄するために追加のクラスが必要になるため、より複雑になりますが、この複雑さは、解決される問題に固有のものです。良い点は、靴ひもを処分するためだけにバスを使い捨てにする必要がなくなったことです。

3
Joh

これは、基本的に、CompositionまたはAggregationとDisposableクラスを組み合わせたときに発生することです。前述のように、最初の方法は、靴ひもからwaitHandleをリファクタリングすることです。

そうは言っても、アンマネージリソースがない場合は、Disposableパターンを大幅に削減できます。 (私はまだこれについての公式リファレンスを探しています。)

ただし、デストラクタとGC.SuppressFinalize(this);は省略できます。仮想ボイドDispose(bool disposing)を少しクリーンアップします。

3
Henk Holterman

興味深いことに、Driverが上記のように定義されている場合:

class Driver
{
    Shoe[] shoes = { new Shoe(), new Shoe() };
}

次に、ShoeIDisposableになったとき、FxCop(v1.36)はDriverIDisposableであるべきだと文句を言わない。

ただし、次のように定義されている場合:

class Driver
{
    Shoe leftShoe = new Shoe();
    Shoe rightShoe = new Shoe();
}

それから文句を言うでしょう。

最初のバージョンではShoeインスタンスがDriverによってまだ作成されており、何らかの方法で破棄する必要があるため、これはソリューションではなくFxCopの制限にすぎないと思います。

3
GrahamS

制御の反転を使用するのはどうですか?

class Bus
{
    private Driver busDriver;

    public Bus(Driver busDriver)
    {
        this.busDriver = busDriver;
    }
}

class Driver
{
    private Shoe[] shoes;

    public Driver(Shoe[] shoes)
    {
        this.shoes = shoes;
    }
}

class Shoe
{
    private Shoelace lace;

    public Shoe(Shoelace lace)
    {
        this.lace = lace;
    }
}

class Shoelace
{
    bool tied;
    private AutoResetEvent waitHandle;

    public Shoelace(bool tied, AutoResetEvent waitHandle)
    {
        this.tied = tied;
        this.waitHandle = waitHandle;
    }
}

class Program
{
    static void Main(string[] args)
    {
        using (var leftShoeWaitHandle = new AutoResetEvent(false))
        using (var rightShoeWaitHandle = new AutoResetEvent(false))
        {
            var bus = new Bus(new Driver(new[] {new Shoe(new Shoelace(false, leftShoeWaitHandle)),new Shoe(new Shoelace(false, rightShoeWaitHandle))}));
        }
    }
}
1
Darragh