32

SomeDisposableObject実装するクラスがある場合IDisposable:

class SomeDisposableObject : IDisposable
{
    public void Dispose()
    {
        // Do some important disposal work.
    }
}

そして、パブリック プロパティとしてAContainerのインスタンスを持つ という別のクラスがあります。SomeDisposableObject

class AContainer
{
    SomeDisposableObject m_someObject = new SomeDisposableObject();

    public SomeDisposableObject SomeObject
    {
        get { return m_someObject; }
        set { m_someObject = value; }
    }
}

AContainerそれから FxCop は、それも行われていると主張しますIDisposable

これは問題ありませんが、別のクラスがまだインスタンスへの参照を持っている可能性があるため、m_someObject.Dispose()から安全に呼び出す方法がわかりません。AContainer.Dispose()m_someObject

このシナリオを回避する最善の方法は何ですか?

AContainer.SomeObject(他のコードは常に null 以外の値を持つことに依存していると仮定すると、単にインスタンスの作成を の外に移動することAContainerはオプションではありません)

編集:一部のコメント投稿者が問題を見逃していると思うので、いくつかの例で拡張します。m_someObject.Dispose() を呼び出すDispose()メソッドを実装しただけでは、次のような状況が発生します。AContainer

// Example One
AContainer container1 = new AContainer();
SomeDisposableObject obj1 = container1.SomeObject;
container1.Dispose();
obj1.DoSomething(); // BAD because obj1 has been disposed by container1.

// Example Two
AContainer container2 = new AContainer();
SomeObject obj2 = new SomeObject();
container2.SomeObject = obj2; // BAD because the previous value of SomeObject not disposed.
container2.Dispose();
obj2.DoSomething(); // BAD because obj2 has been disposed by container2, which doesn't really "own" it anyway.  

それは役に立ちますか?

4

10 に答える 10

25

Jon Skeet が指摘しているように、重要なポイントは、プロパティによって表される使い捨てリソースの所有権です。

.NET Framework の例を見ると役立つ場合があります。動作が異なる 3 つの例を次に示します。

  • コンテナは常に を破棄します。System.IO.StreamReader は、使い捨てプロパティ BaseStream を公開します。基になるストリームを所有していると見なされ、StreamReader を破棄すると常に基になるストリームが破棄されます。

  • コンテナは決して処分しません。System.DirectoryServices.DirectoryEntry は、Parent プロパティを公開します。親を所有しているとは見なされないため、DirectoryEntry を破棄しても親は破棄されません。

    この場合、Parent プロパティが逆参照されるたびに新しい DirectoryEntry インスタンスが返され、呼び出し元はおそらくそれを破棄することが期待されます。おそらく、これはプロパティのガイドラインに違反しており、おそらく代わりに GetParent() メソッドが必要です。

  • コンテナが廃棄する場合があります。System.Data.SqlClient.SqlDataReader は、使い捨ての Connection プロパティを公開しますが、呼び出し元は、SqlCommand.ExecuteReader の CommandBehavior 引数を使用して、基になる接続をリーダーが所有する (したがって破棄する) かどうかを決定します。

もう 1 つの興味深い例は System.DirectoryServices.DirectorySearcher で、これには読み取り/書き込みの使い捨てプロパティ SearchRoot があります。このプロパティが外部から設定されている場合、基になるリソースは所有されていないと見なされるため、コンテナーによって破棄されません。外部から設定されていない場合、内部で参照が生成され、破棄されるようにフラグが設定されます。これはルッツリフレクターで確認できます。

コンテナがリソースを所有しているかどうかを判断し、その動作を正確に文書化する必要があります。

リソースを所有していると判断し、プロパティが読み取り/書き込み可能である場合は、セッターが置き換えている参照を破棄するようにする必要があります。次に例を示します。

public SomeDisposableObject SomeObject    
{        
    get { return m_someObject; }        
    set 
    { 
        if ((m_someObject != null) && 
            (!object.ReferenceEquals(m_someObject, value))
        {
            m_someObject.Dispose();
        }
        m_someObject = value; 
    }    
}
private SomeDisposableObject m_someObject;

UPDATE : GrahamS はコメントで、破棄する前にセッターで m_someObject != 値をテストする方がよいと正しく指摘しています: これを考慮して上記の例を更新しました (!= ではなく ReferenceEquals を使用して明示的にします)。多くの現実のシナリオでは、セッターの存在は、オブジェクトがコンテナーによって所有されていないことを意味する可能性があるため、破棄されません。

于 2009-03-23T20:31:42.223 に答える
15

それは、誰が使い捨てオブジェクトを概念的に「所有」するかに本当に依存します。場合によっては、クラスがオブジェクトをクリーンアップする責任を負わずに、たとえばコンストラクターでオブジェクトを渡せるようにしたいことがあります。また、自分でクリーンアップしたい場合もあります。(サンプルコードのように)オブジェクトを作成している場合は、ほぼ確実にオブジェクトをクリーンアップする必要があります。

物件に関しては、物件を持っているからといって、実際に所有権などを譲渡するべきではないと思います。あなたのタイプがオブジェクトの処分に責任がある場合、それはその責任を維持する必要があります。

于 2009-03-23T19:32:45.610 に答える
5

本当の問題は、オブジェクト指向の設計かもしれません。AContainerが破棄される場合、そのすべてのメンバーオブジェクトも破棄する必要があります。そうでない場合は、体を処分できるように聞こえますが、脚のインスタンスを存続させたいと考えています。正しく聞こえません。

于 2009-03-23T19:57:01.293 に答える
4

クラスに使い捨てオブジェクトがある場合は、ラップされた使い捨てを破棄するメソッドを使用して実装IDisposableします。Disposeここで、呼び出し元のコードは、それが使用されていること、またはオブジェクトを破棄するusing()同等のtry/コードであることを確認する必要があります。finally

于 2009-03-23T19:27:48.413 に答える
3

私は自分の質問に答えようとします:

そもそも避ける

この状況から抜け出す最も簡単な方法は、コードをリファクタリングして問題を完全に回避することです。
これを行うには 2 つの明白な方法があります。

外部インスタンスの作成 インスタンスを作成せず、代わりにそれを提供するために外部コードに依存する場合、インスタンスを「所有」しなくなり、それ
を破棄する責任を負いません。AContainerSomeDisposableObjectAContainer

外部で作成されたインスタンスは、コンストラクターを介して、またはプロパティを設定することによって提供できます。

public class AContainerClass
{
    SomeDisposableObject m_someObject; // No creation here.

    public AContainerClass(SomeDisposableObject someObject)
    {
        m_someObject = someObject;
    }

    public SomeDisposableObject SomeObject
    {
        get { return m_someObject; }
        set { m_someObject = value; }
    }
}

インスタンスを非公開に保つ
投稿されたコードの主な問題は、所有権が混乱していることです。Dispose 時に、AContainerクラスはインスタンスの所有者を判別できません。作成したインスタンスである場合もあれば、外部でsetプロパティを介して作成された他のインスタンスである場合もあります。

これを追跡し、作成したインスタンスを処理していることを確実に認識している場合でも、他のクラスがパブリック プロパティから取得したインスタンスへの参照を持っている可能性があるため、安全に破棄することはできません。

インスタンスを公開しないようにコードをリファクタリングできる場合 (つまり、プロパティを完全に削除することによって)、問題は解消されます。

そして、避けられない場合...

何らかの理由でコードをこれらの方法でリファクタリングできない場合 (質問で規定したように)、私の意見では、かなり難しい設計の選択が残されています。

常にインスタンスを破棄する
このアプローチを選択すると、プロパティが設定されたときにインスタンスAContainerの所有権を取得することを効果的に宣言することになります。SomeDisposableObject

これは状況によっては意味があります。特にSomeDisposableObjectが明らかに一時的または従属的なオブジェクトである場合です。ただし、呼び出し元のコードがこの所有権の譲渡を認識している必要があるため、慎重に文書化する必要があります。

(メソッド名を使用して所有権に関するさらなるヒントを得ることができるため、プロパティではなくメソッドを使用する方が適切な場合があります)。

public class AContainerClass: IDisposable
{
    SomeDisposableObject m_someObject = new SomeDisposableObject();

    public SomeDisposableObject SomeObject
    {
        get { return m_someObject; }
        set 
        {
            if (m_someObject != null && m_someObject != value)
                m_someObject.Dispose();

            m_someObject = value;
        }
    }

    public void Dispose()
    {
        if (m_someObject != null)
            m_someObject.Dispose();

        GC.SuppressFinalize(this);
    }
}

元のインスタンスのままである場合にのみ破棄する
このアプローチでは、インスタンスが最初に作成されたものから変更されたかどうかを追跡し、元のインスタンスである場合にのみ破棄しますAContainer。ここでは、所有モデルが混在しています。AContainer独自のSomeDisposableObjectインスタンスの所有者のままですが、外部インスタンスが提供されている場合、それを破棄するのは外部コードの責任のままです。

このアプローチは、ここでの実際の状況を最もよく反映していますが、正しく実装するのは難しい場合があります。クライアント コードは、次のような操作を実行することで問題を引き起こす可能性があります。

AContainerClass aContainer = new AContainerClass();
SomeDisposableObject originalInstance = aContainer.SomeObject;
aContainer.SomeObject = new SomeDisposableObject();
aContainer.DoSomething();
aContainer.SomeObject = originalInstance;

ここでは、新しいインスタンスがスワップインされ、メソッドが呼び出され、元のインスタンスが復元されました。残念ながら、元のインスタンスが置き換えられたときにAContainer呼び出さDispose()れたため、現在は無効になっています。

諦めて GC に処理させる
これは明らかに理想的とは言えません。SomeDisposableObjectクラスに本当に希少なリソースが含まれている場合は、すぐに破棄しないと間違いなく問題が発生します。

ただし、 がインスタンスの所有権をどのように扱うAContainerかについての特別な知識を必要としないため、クライアント コードがどのように対話するかという点で最も堅牢なアプローチを表す場合もあります。AContainerSomeDisposableObject

システムで使い捨てリソースが実際に不足していないことがわかっている場合は、これが実際に最良のアプローチである可能性があります。


一部のコメンターは、参照カウントを使用して、他のクラスがまだSomeDisposableObjectインスタンスへの参照を持っているかどうかを追跡できる可能性があることを示唆しています。これは、安全であることがわかっている場合にのみ破棄し、それ以外の場合は GC に処理させることができるため、非常に便利です。

ただし、オブジェクトの参照カウントを決定するための C#/.NET API については知りません。ある場合は、私に知らせてください。

于 2009-03-24T12:03:17.537 に答える
2

のインスタンスをDispose()安全に呼び出すことができない理由は、カプセル化の欠如によるものです。public プロパティは、内部状態の一部への無制限のアクセスを提供します。内部状態のこの部分は IDisposable プロトコルの規則に従う必要があるため、適切にカプセル化されていることを確認することが重要です。AContainerSomeDisposableObject

この問題は、ロックに使用されるインスタンスへのアクセスを許可する場合と似ています。これを行うと、ロックが取得された場所を特定するのが非常に難しくなります。

使い捨てインスタンスの公開を避けることができれば、呼び出しを誰が処理するかという問題Dispose()も解消されます。

于 2009-03-23T20:21:45.787 に答える
1

私が遭遇した興味深いことは、通常、SqlCommand が SqlConnection (どちらも IDisposable を実装する) インスタンスを所有していることです。ただし、SqlCommand で dispose を呼び出しても、接続は破棄されません

Stackoverflow right hereの助けを借りて、これも発見しました。

つまり、「子」(ネストされた?) インスタンスを後で再利用できるかどうかが重要です。

于 2009-03-24T12:22:19.927 に答える
0

一般に、オブジェクトを作成した人は誰でも廃棄の責任を負うべきだと思います。この場合、AContainer は SomeDisposableObject を作成するため、AContainer の場合は Disposed にする必要があります。

何らかの理由で、SomeDisposableObject が AContainer よりも長く存続する必要があると思われる場合は、次の方法しか考えられません。

  • SomeDisposableObject を unDisposed のままにします。この場合、GC が処理します。
  • SomeDisposableObject に AContainer への参照を与えます (WinForms コントロールと親プロパティを参照してください)。SomeDisposableObject が到達可能である限り、AContainer も到達可能です。これにより、GC は AContainer を破棄できなくなりますが、誰かが Dispose を手動で呼び出した場合は、SomeDisposableObject を Dispose することになります。予想通りと言えます。
  • CreateSomeDisposableObject() など、SomeDisposableObject をメソッドとして実装します。これにより、クライアントが廃棄の責任を負うことが明確になります。

とはいえ、全体として、デザインが理にかなっているのかどうかはよくわかりません。結局のところ、次のようなクライアント コードを期待しているようです。

SomeDisposableObject d;
using (var c = new AContainer()) {
   d = c.SomeObject;
}
// do something with d

それは壊れたクライアント コードのように思えます。それはデメテルの法則に違反しており、私にとってはごく普通の常識です。

于 2009-03-23T20:24:25.547 に答える
0

ここで言及した設計は、このシナリオを処理できるものではありません。あなたは、そのクラスのコンテナがあると言ったので、それ自体と一緒にそれを破棄する必要があります。他のオブジェクトがそれを使用している可能性がある場合、それはコンテナではなく、クラスのスコープが広がり、そのスコープの境界で破棄する必要があります。

于 2009-03-23T22:32:56.487 に答える
-1

Dispose()でDisposalにフラグを立てることができます。結局のところ、Disposalはデストラクタではありません-オブジェクトはまだ存在しています。

それで:

class AContainer : IDisposable
{
    bool _isDisposed=false;

    public void Dispose()
    {
        if (!_isDisposed) 
        {
           // dispose
        }
        _isDisposed=true;
    }
}

これを他のクラスにも追加します。

于 2009-03-23T19:30:31.900 に答える