次のような単純なクラスのセットがあるとします。
_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つのShoe
sがあり、各Shoe
にはShoelace
があります。すべて非常にばかげています。
後で、Shoelace
の一部の操作をマルチスレッド化できると判断したため、スレッドが通信するためのEventWaitHandle
を追加します。したがって、Shoelace
は次のようになります。
_class Shoelace
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
// ... other stuff ..
}
_
しかし、現在 MicrosoftのFxCop は文句を言うでしょう: "次のIDisposable型のメンバーを作成するため、 'Shoelace'にIDisposableを実装します: 'EventWaitHandle'。"
さて、IDisposable
にShoelace
を実装すると、きちんとした小さなクラスがこの恐ろしい混乱になります。
_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);
}
}
_
正解です。しかし、FxCopはShoe
がShoelace
を作成することに不満を言うので、Shoe
もIDisposable
でなければなりません。
そしてDriver
はShoe
を作成するので、Driver
はIDisposable
でなければなりません。そしてBus
はDriver
を作成するので、Bus
はIDisposable
でなければなりません。
突然Shoelace
への小さな変更が原因で多くの作業が発生し、上司はBus
をチェックアウトしてShoelace
に変更する必要があるのかと不思議に思っています。
このIDisposable
の蔓延をどのように防止しながら、アンマネージオブジェクトが適切に破棄されるようにしますか?
IDisposableが拡散するのを実際に「防ぐ」ことはできません。 AutoResetEvent
などの一部のクラスは破棄する必要があります。最も効率的な方法は、ファイナライザのオーバーヘッドを回避するためにDispose()
メソッドで破棄することです。ただし、このメソッドは何らかの方法で呼び出す必要があります。そのため、例のように、IDisposableをカプセル化または含むクラスはこれらを破棄する必要があるため、それらも破棄する必要があります。これを回避する唯一の方法は、次のとおりです。
using
パターン)IDisposableはオプションのケースをサポートしているため、無視できる場合があります。たとえば、WaitHandleはIDisposableを実装して、名前付きミューテックスをサポートします。名前が使用されていない場合、Disposeメソッドは何もしません。 MemoryStreamは別の例で、システムリソースを使用せず、そのDispose実装も何もしません。アンマネージリソースが使用されているかどうかを慎重に検討することは、教育に役立ちます。したがって、.netライブラリの利用可能なソースを調べたり、逆コンパイラを使用したりできます。
正確さの点では、親オブジェクトが使い捨てにする必要がある子オブジェクトを作成して本質的に所有している場合、オブジェクト関係を介したIDisposableの拡散を防ぐことはできません。この状況ではFxCopは正しく、親はIDisposableである必要があります。
オブジェクト階層のリーフクラスにIDisposableを追加しないようにすることができます。これは必ずしも簡単な作業ではありませんが、興味深い演習です。論理的な観点から、ShoeLaceが使い捨てである必要がある理由はありません。ここにWaitHandleを追加する代わりに、ShoeLaceとWaitHandleの間の関連付けを、それが使用される時点で追加することもできます。最も簡単な方法は、Dictionaryインスタンスを使用することです。
WaitHandleが実際に使用された時点で、マップを介してWaitHandleを緩い関連付けに移動できる場合は、このチェーンを解除できます。
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();
}
}
}
待機ハンドルのスコープはTie
methodに制限されており、クラスにディスポーザブルフィールドを含める必要がないため、クラス自体をディスポーザブルにする必要はありません。
待機ハンドルはShoelace
内の実装の詳細であるため、宣言に新しいインターフェースを追加するなど、パブリックインターフェースを変更しないでください。使い捨てフィールドが不要になった場合はどうなりますか。IDisposable
宣言を削除しますか? Shoelace
について考えると 抽象化、あなたはそれがIDisposable
のようなインフラストラクチャの依存関係によって汚染されるべきではないことをすぐに理解します。 IDisposable
は、決定論的なクリーンアップを必要とするリソースを抽象化してカプセル化するクラス用に予約する必要があります。つまり、使い捨てがクラスの一部であるクラスの場合 抽象化。
これは、「クイックフィックス」が泥沼に発展する場合によくあるように、より高いレベルの設計問題のように感じられます。方法の詳細については、 this thread が役立つかもしれません。
デザインを非常に緊密に結合している場合、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
{
...
}
新しいデザインは、残念なことに、インスタンスを作成して靴とドライバーの具体的なインスタンスを破棄するために追加のクラスが必要になるため、より複雑になりますが、この複雑さは、解決される問題に固有のものです。良い点は、靴ひもを処分するためだけにバスを使い捨てにする必要がなくなったことです。
これは、基本的に、CompositionまたはAggregationとDisposableクラスを組み合わせたときに発生することです。前述のように、最初の方法は、靴ひもからwaitHandleをリファクタリングすることです。
そうは言っても、アンマネージリソースがない場合は、Disposableパターンを大幅に削減できます。 (私はまだこれについての公式リファレンスを探しています。)
ただし、デストラクタとGC.SuppressFinalize(this);は省略できます。仮想ボイドDispose(bool disposing)を少しクリーンアップします。
興味深いことに、Driver
が上記のように定義されている場合:
class Driver
{
Shoe[] shoes = { new Shoe(), new Shoe() };
}
次に、Shoe
がIDisposable
になったとき、FxCop(v1.36)はDriver
もIDisposable
であるべきだと文句を言わない。
ただし、次のように定義されている場合:
class Driver
{
Shoe leftShoe = new Shoe();
Shoe rightShoe = new Shoe();
}
それから文句を言うでしょう。
最初のバージョンではShoe
インスタンスがDriver
によってまだ作成されており、何らかの方法で破棄する必要があるため、これはソリューションではなくFxCopの制限にすぎないと思います。
制御の反転を使用するのはどうですか?
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))}));
}
}
}