2012-05-09 10 views
29

follwing状況最低料金:クラスまたはインターフェイスのIDisposableを宣言しますか?

public interface ISample 
{ 
} 

public class SampleA : ISample 
{ 
    // has some (unmanaged) resources that needs to be disposed 
} 

public class SampleB : ISample 
{ 
    // has no resources that needs to be disposed 
} 

クラスSampleAは、リソースを解放するためのインターフェイスIDisposableインターを実装する必要があります。 次の2つの方法でこれを解決できます。それを実装するためのインタフェースISampleと力派生クラスに追加します

public class SampleA : ISample, IDisposable 
{ 
    // has some (unmanaged) resources that needs to be disposed 
} 

2:

1.クラスSampleAするために必要なインタフェースを追加します。

public interface ISample : IDisposable 
{ 
} 

あなたは、彼らが処分することは何もない場合でも、IDisposableインターを実装するために任意の実装を強制するインタフェースにそれを置く場合。一方、インターフェイスの具体的な実装にはdispose/usingブロックが必要であり、クリーンアップのためにIDisposableとしてキャストする必要はありません。両方の方法でさらにいくつかの長所/短所があるかもしれません...なぜ、他の方法より優先される方法を使用するように提案しますか?

+4

インタフェースに対して書かれたコード(おそらく既に構築されたインスタンスが渡されたと思われる)は、そのインスタンスの(有効な)有効期間を終了させる責任はありますか? –

+1

@Damien_The_Unbeliever:いいえ、ISampleはファクトリメソッドの結果またはDependency Injectionから来ると仮定します。 – Beachwalker

+2

それは事です - もし工場から来たら*あなたのコード*は処分の責任を負う可能性があります - それで私はそれをInterfaceに載せます。しかし、注入されている場合は、インジェクタが生涯責任を負うと仮定しているので、インタフェースには適合しません。質問には一応の答えがあるとは思いません。 –

答えて

6

すべてのあなたのインターフェイスにusing(){}パターンを適用した場合のインタフェースを設計親指のルールは、「使いやすさ」使いやすさの-実装」上をを好むことであるので、それはISampleIDisposableから派生持つのがベストです"。少なくともいくつかのいくつかの実施例は、IDisposableを実施する可能性がある場合

+0

foreachでusingステートメントをどのように使用しますか? –

+1

"...あなたはインターフェースを見ているだけです..."これが鍵です。多くの優れたOOPソリューションはインターフェイスのみを使用するため、インターフェイスでIDisposableを実装することは意味があると思います。そうすれば、消費するコードはすべてのサブクラスを同じように扱うことができます。 –

6

個人的には、すべてISampleさんが使い捨てでなければならない場合は、私はそれをインターフェースに置いておきます。

あなたは後者の場合のように聞こえます。

+0

しかし、あなたのlibのユーザがそれを知らず、処分する必要がある実装があるかもしれない場合、Dispose()の呼び出しを確実にする方法はありますか? – Beachwalker

+0

@Stegi - 簡単。もし '' IDisposable''をテストし、 '' Dispose''を呼び出してください。 – Jamiec

+0

はい、私は知っていますが、これはコードを駄目にします。他のすべての実装(IList、I ...など)がIDisposableになる可能性があるためです。 IDisposableは一般的なオブジェクト基本クラスの実装(たとえ空であっても)でなければなりません。 – Beachwalker

0

個人的には、2つの具体例を作成しない限り、私は1を選択します。 2人の良い例はIListです。

IListは、コレクション用のインデクサーを実装する必要があることを意味します。 しかし、IListは実際にはIEnumerableであることを意味し、クラスにはGetEnumerator()が必要です。あなたがISampleを実装するクラスは、あなたのインターフェイスを実装していないすべてのクラスがIDisposableが、その後にそれらを強制しない実装している場合、IDisposableを実装する必要があるとhesistantあるあなたの場合は

具体的には、IDispoableに焦点を当てると、特にIDispoableは、クラスを使用しているプログラマが合理的に醜いコードを書くことを強いられます。例えば、

foreach(item in IEnumerable<ISample> items) 
{ 
    try 
    { 
     // Do stuff with item 
    } 
    finally 
    { 
     IDisposable amIDisposable = item as IDisposable; 
     if(amIDisposable != null) 
      amIDisposable.Dispose(); 
    } 
} 

だけでなく、コードは恐ろしいです、最終的にはちょうどに戻ってもDispose()た場合、そのリストのすべての反復のためのアイテムを処分するためにブロックがある確保する上で重要なパフォーマンスの低下が発生します実装。

ここにコメントの1つに答えるコードを貼り付け、読みやすくしてください。

+0

しかし、実装オブジェクトを廃棄する必要があるかどうかを示すファクトリ(IoCコンテナの簡単な使用法)があるとどうなりますか?明示的にキャストして呼び出すことによってDispose()を呼び出す必要があります。このようにして、あなたのコードは(可能な)実装についての知識を必要とします。それ以外の場合は、非常に簡単なブロックを使用することができます。 – Beachwalker

+0

@Stegi using blockは醜いように見えるかもしれませんが、それでもパフォーマンス上のペナルティはあります。また、foreachの内部でブロックを使用する方法などはわかりません。マイクロソフトのコードは、これを行う例では、IDisposable amIDisposable =オブジェクトIDisposableとしてlitered; if(amIDisposable!= null)amIDisposable.Dispose(); IDisposableにキャストできない場合は例外をスローしないので、パフォーマンスのペナルティはほとんど存在しません。 –

+0

特定の型を実装または継承するクラスの1%だけが 'IDisposable.Dispose'の中で何かをしても、その型として宣言された変数またはフィールドに対して' IDisposable.Dispose'を呼び出す必要がある場合(実装型または派生型のいずれかとは対照的に)、それは型自体が 'IDisposable'を継承または実装する良い兆候です。実行時に、オブジェクトインスタンスが 'IDisposable'を実装しているかどうかをテストし、そうであれば' IDisposable.Dispose'を呼び出します(非ジェネリックな 'IEnumerable'で必要だったもの)はメジャーコードの匂いです。 – supercat

2

IDispoableは非常に一般的なインターフェイスなので、インターフェイスを継承しても問題ありません。したがって、ISample実装の一部にノーオペレーションを実装するためには、コードの型チェックを犠牲にする必要がありません。だからこの第2の選択肢はこの観点からより良いかもしれない。

+0

それは単に真実ではありません。あなたのクラスにIDisposableを実装するフィールドがある場合、そのクラスはIDisposableでなければならず、フィールドの廃棄を呼び出す必要があります。あなたがIDisposableを必要としないときに追加すると、半分のクラスがIDisposableになります。 –

3

インターフェースIFooおそらくIDisposableを実装する必要があり、少なくともいくつかの機会にインスタンスへの最後の生き残り参照型IFooの変数またはフィールドに格納されます。いずれの実装でもIDisposableが実装されており、インスタンスがファクトリインターフェイス(IEnumerator<T>のインスタンスの場合のように、多くの場合、ファクトリインターフェイスIEnumerable<T>によって作成されます)を使用して作成される場合は、ほとんど確実にIDisposableを実装する必要があります。

IEnumerable<T>IEnumerator<T>を比較することは有益です。 IEnumerable<T>を実装するいくつかの型もIDisposableを実装していますが、そのような型のインスタンスを作成するコードは、それらが何であるかを知り、処分が必要であることを知り、特定の型として使用します。そのようなインスタンスはタイプIEnumerable<T>として他のルーチンに渡すことができ、それらの他のルーチンはオブジェクトが最終的に処分する必要があるという手がかりを持たないだろうが、ほとんどの場合、オブジェクトへの参照を保持する最後のルーチン。対照的に、IEnumerator<T>のインスタンスは、多くの場合、IEnumerable<T>によって返されるという事実を超えて、それらのインスタンスの基礎となる型について何も知らないコードによって、作成され、使用され、最終的に放棄されます。 IEnumerable<T>.GetEnumerator()戻り実装のIEnumerator<T>の一部の実装では、それらが破棄される前にIDisposable.Disposeメソッドが呼び出されていない場合にリソースがリークし、IEnumerable<T>のパラメータを受け入れるほとんどのコードは、そのような型が渡されるかどうかを知る方法を持ちません。 IEnumerable<T>には、返されたIEnumerator<T>が廃棄される必要があるかどうかを示すためにプロパティEnumeratorTypeNeedsDisposalを含めることが可能ですが、GetEnumerator()を呼び出すルーチンが返されたオブジェクトのタイプをチェックして、IDisposableを実装しているかどうかを確認することができます。無条件にDisposeメソッドを呼び出して、何もしない可能性があります。Disposeが必要かどうかを判断し、そうでない場合にのみコールします。あなたはあなたがA.

に追加する必要がありますに興味を持っていないクライアントにメソッドを与えているインタフェースにIDisposableをを追加した場合SOLIDInteface Segregation Principleに続いて

+0

'IEnumerator 'は、ライフタイム管理が確実にAPIの一部であるインターフェースの良い例です。これはおそらく事をより簡単にするために行われたものです。しかし、あるメソッドでは 'GetEnumerator() 'を呼び出し、ある時点では列挙子を別のメソッドに渡したいと考えられます。他の方法では理想的には 'Dispose()'にアクセスできません。フレームワークに 'interface IDisposableEnumerator :IEnumerator 、IDisposable {}'および 'IEnumeratorインターフェイス:IEnumerator {T Current {get; }} '、これはいくつかのものをよりクリーンにすることができます(しかし複雑すぎる: - /)。 – binki

+1

@binki: 'IEnumerator'が' IDisposable'を実装するためには何がきれいだったでしょうか。何も知らないIEnumerableを受け取るコードは、それが返すIEnumeratorに対して 'Dispose'を呼び出す必要があると推測しなければなりません。返された列挙子が後で消費するために他のメソッドに渡された場合、後者のメソッドはそれに 'Dispose'を呼び出す責任があります。 'IDisposable'を実装することが知られているものに無駄な' Dispose'を呼び出すことは、オブジェクトが 'IDisposable'を実装しているかどうかをチェックするよりも速いので... – supercat

+1

...オブジェクトが義務を果たす最も簡単な方法は、それらの列挙子が気にするかどうかにかかわらず、彼らを放棄する前に彼らが所有する列挙子に「廃棄」します。 – supercat

16

は、それとは別に、インターフェースがあるため使い捨てになることはありませんディスポーザビリティは、インターフェイスの具体的な実装に関連するものであり、インターフェイス自体では決してありません。

任意のインターフェイスは、処理する必要のある要素の有無にかかわらず、実装することができます。

1

私は、インターフェイスにIDisposableを置くといくつかの問題を引き起こす可能性があると考え始めています。これは、そのインタフェースを実装するすべてのオブジェクトの存続期間が安全に同期して終了できることを意味します。すなわち、それは誰もがこのようなコードを記述することができますし、IDisposableをサポートするためにすべての実装を必要とします。具体的な種類にアクセスしている

using (ISample myInstance = GetISampleInstance()) 
{ 
    myInstance.DoSomething(); 
} 

のみコードは、オブジェクトの寿命を制御するための正しい方法を知ることができます。たとえば、タイプが最初に処分する必要がない場合は、IDisposableをサポートしているか、使用後に非同期クリーンアッププロセスawaitingが必要になる場合があります(例:something like option 2 here)。

インターフェイスの作成者は、クラスを実装するための将来の生涯/スコープ管理の必要性をすべて予測することはできません。インタフェースの目的は、オブジェクトが一部のAPIを公開して、一部のコンシューマに役立つようにすることです。一部のインターフェイスとなる可能性があります(IDisposableなど)。これらのインターフェイスを生涯管理とは無関係に混在させると、インターフェイスの実装を困難または不可能にすることができます。インタフェースの実装がほとんどなく、インタフェースのコンシューマとライフタイムとスコープマネージャが同じメソッドになるようにコードを構造化する場合、この区別は最初は明確ではありません。しかし、あなたがあなたのオブジェクトを周りに渡すようになると、これはより明確になります。上記のコードサンプルで

void ConsumeSample(ISample sample) 
{ 
    // INCORRECT CODE! 
    // It is a developer mistake to write “using” in consumer code. 
    // “using” should only be used by the code that is managing the lifetime. 
    using (sample) 
    { 
     sample.DoSomething(); 
    } 

    // CORRECT CODE 
    sample.DoSomething(); 
} 

async Task ManageObjectLifetimesAsync() 
{ 
    SampleB sampleB = new SampleB(); 
    using (SampleA sampleA = new SampleA()) 
    { 
     DoSomething(sampleA); 
     DoSomething(sampleB); 
     DoSomething(sampleA); 
    } 

    DoSomething(sampleB); 

    // In the future you may have an implementation of ISample 
    // which requires a completely different type of lifetime 
    // management than IDisposable: 
    SampleC = new SampleC(); 
    DoSomething(sampleC); 
    sampleC.Complete(); 
    await sampleC.Completion; 
} 

class SampleC : ISample 
{ 
    public void Complete(); 
    public Task Completion { get; } 
} 

、私はあなたが設けられた二つに追加して、寿命管理シナリオの3種類を実証しました。

  1. SampleA

    は、同期using() {}サポートとIDisposableです。
  2. SampleBは純粋なガベージコレクションを使用します(リソースを消費しません)。
  3. SampleCは、生存期間の最後にawaitが必要です(リソースを消費し、非同期に発生した例外を吹き飛ばすことを寿命管理コードに通知するため)。寿命管理を維持することにより

を使用すると、開発者のミスを防ぐ(Dispose()に例えば、偶然の呼び出し)と、よりきれいに、将来の不測の生涯/スコープ管理パターンをサポートすることができ、あなたの他のインタフェースとは別。

+0

クリーンアップを必要としない型は、 'IDisposable'を' void IDisposable.Dispose(){}; 'を介して簡単に正しく実装することができます。ファクトリ関数から返されたオブジェクトの中にはクリーンアップが必要なものがある場合、 'IDisposable'を実装する型を返す方が簡単で簡単です。そして、クライアントが返されたオブジェクトの' Dispose'を呼び出すと、クリーンアップが必要なオブジェクトをクライアントが判断する必要があります。 – supercat

関連する問題