web-dev-qa-db-ja.com

IDisposableを正しく実装していますか?

このクラスはStreamWriterを使用するため、IDisposableを実装します。

_public class Foo : IDisposable
{
    private StreamWriter _Writer;

    public Foo (String path)
    {
        // here happens something along the lines of:
        FileStream fileWrite = File.Open (path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
        _Writer = new StreamWriter (fileWrite, new ASCIIEncoding ());
    }

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

    ~Foo()
    {
        Dispose (false);
    }

    protected virtual void Dispose (bool disposing)
    {
        if (_Disposed) {
            return;
        }
        if (disposing) {
            _Writer.Dispose ();
        }
        _Writer = null;
        _Disposed = true;
    }
    private bool _Disposed;
}
_

}

現在の実装に問題はありますか?つまり、基になるFileStreamを手動で解放する必要がありますか? Dispose(bool)は正しく書かれていますか?

28
mafu

クラスがアンマネージリソースを直接使用しない場合は、この広範なバージョンのIDisposable実装を使用する必要はありません。

シンプルな

 public virtual void Dispose()
 {

     _Writer.Dispose();
 }

十分であろう。

コンシューマーがオブジェクトの破棄に失敗した場合、Disposeを呼び出さずに通常はGCされ、_Writerによって保持されているオブジェクトもGCされ、ファイナライザーがあるため、管理されていないリソースを適切にクリーンアップできます。

編集

Mattや他の人から提供されたリンクについて調査した結果、ここでの私の答えであるという結論に達しました。理由は次のとおりです。-

継承可能なクラスでの使い捨て実装「パターン」(つまり、保護された仮想Dispose(bool)、SuppressFinalizeなどmarlarky)の背後にある前提は、サブクラスmightが管理されていないクラスを保持することです。資源。

ただし、現実の世界では、私たち.NET開発者の大多数は、管理されていないリソースの近くに行くことはありません。上記の "might"を定量化する必要がある場合、.NETコーディングの種類についてどのような確率の数値を思い付くでしょうか。

私がPersonタイプを持っていると仮定しましょう(議論のために、そのフィールドの1つに使い捨てタイプがあるため、それ自体が使い捨てである必要があります)。これで、Customer、Employeeなどの継承者ができました。誰かがPersonを継承し、管理されていないリソースを保持したい場合に備えて、Personクラスをこの「パターン」で乱雑にするのは本当に合理的ですか。

開発者は、そのような状況の相対的な確率に関する常識を使用せずに、考えられるすべての状況をコーディングしようとして、物事を複雑にしすぎることがあります。

管理されていないリソースを直接使用したい場合、賢明なパターンは、完全な「ディスポーザブルパターン」が妥当である独自のクラスでそのようなものをラップすることです。したがって、「通常の」コードの非常に大きな本体では、そのすべての混乱について心配する必要はありません。 IDisposableが必要な場合は、継承可能かどうかに関係なく、上記の単純なパターンを使用できます。

ふぅ、それを胸から外してよかった。 ;)

39
AnthonyWJones

アンマネージオブジェクトがないため、Finalize(デストラクタ)メソッドは必要ありません。ただし、Fooから継承するクラスにアンマネージオブジェクト、つまりファイナライザーがある場合は、_GC.SuppressFinalize_の呼び出しを維持する必要があります。

クラスを封印すると、アンマネージオブジェクトが方程式に入ることがないことがわかるので、protected virtual Dispose(bool)オーバーロードまたは_GC.SuppressFinalize_を追加する必要はありません。

編集:

これに対する@AnthonyWJonesの異議は、サブクラスがアンマネージオブジェクトを参照しないことがわかっている場合、Dispose(bool)と_GC.SuppressFinalize_全体が不要であるということです。ただし、この場合は、クラスをinternalではなくpublicにし、Dispose()メソッドをvirtualにする必要があります。自分が何をしているのかを知っている場合は、Microsoftの推奨パターンに従わないでください。ただし、ルールを破る前に、ルールを理解しておく必要があります。

16
Matt Howells

推奨される方法は、管理されていないリソース(ネイティブファイルハンドル、メモリポインターなど)がある場合にのみファイナライザーを使用することです。

私は2つの小さな提案がありますが、

管理対象リソースで以前にDisposeを呼び出したことがあるかどうかをテストするために、「m_Disposed」変数を用意する必要はありません。あなたはそのように同様のコードを使うことができます、

protected virtual void Dispose (bool disposing)
{
    if (disposing) {
        if (_Writer != null)
            _Writer.Dispose ();
    }
    _Writer = null;
}

必要な期間だけファイルを開きます。したがって、あなたの例では、File.Existsを使用してコンストラクター内のファイルの存在をテストし、ファイルの読み取り/書き込みが必要な場合、thenファイルを開いて使用します。

また、単にファイルにテキストを書きたいだけの場合は、File.WriteAllTextまたはFile.OpenText、さらにはASCIIEncodingを使用したテキストファイルを対象としたFile.AppendTextを参照してください。

それ以外は、はい、.NET Disposeパターンを正しく実装しています。

4
Mike J

私はこのようなクラスをたくさん持っています-そして私の推薦は可能な限りクラスを封印することです。 IDisposable +継承は機能しますが、99%の場合、継承は必要ありません。間違いを犯しやすいのです。

パブリックAPIを作成している場合を除いて(この場合、ユーザーが望む方法でコードを実装できるようにするのは良いことです。つまり、IDisposableを使用する)、それをサポートする必要はありません。

ただ行う:

public sealed class Foo : IDisposable
{
    private StreamWriter _Writer;

    public Foo(string path)
    {
            FileStream fileWrite = File.Open (path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
            try { 
                _Writer = new StreamWriter (fileWrite, new ASCIIEncoding());
            } catch {
                fileWrite.Dispose();
                throw;
            }
    }

    public void Dispose()
    {
         _Writer.Dispose();
    }
}

Try ... catchに注意してください。技術的には、StreamWriterコンストラクターが例外をスローする可能性があります。その場合、FileStreamの所有権を取得することはなく、自分で破棄する必要があります。

本当に多くのIDisposablesを使用している場合は、そのコードをC++/CLIに配置することを検討してください。これにより、すべての定型コードが作成されます(ネイティブオブジェクトと管理対象オブジェクトの両方に適切な決定論的破壊技術が透過的に使用されます)。

ウィキペディアには、C++用のまともなIDisposableサンプルがあります(実際、IDisposableが多数ある場合、C++は実際にはC#よりもはるかに単純です): ウィキペディア: C++/CLIファイナライザーと自動変数

たとえば、C++/CLIで「安全な」使い捨てコンテナを実装すると次のようになります...

public ref class MyDisposableContainer
{
    auto_handle<IDisposable> kidObj;
    auto_handle<IDisposable> kidObj2;
public:

    MyDisposableContainer(IDisposable^ a,IDisposable^ b) 
            : kidObj(a), kidObj2(b)
    {
        Console::WriteLine("look ma, no destructor!");
    }
};

このコードは、カスタムIDisposable実装を追加しなくても、kidObjとkidObj2を正しく破棄します。また、Dispose実装の例外に対して堅牢であり(発生するはずではありませんが、それでも)、さらに多くのIDisposableメンバーまたはネイティブリソース。

私がC++/CLIの大ファンというわけではありませんが、リソース指向のコードの場合、C#を簡単に打ち負かすことができ、マネージドコードとネイティブコードの両方との相互運用性が非常に優れています。つまり、完璧なグルーコードです;-)。私はコードの90%をC#で書く傾向がありますが、すべての相互運用のニーズにC++/CLIを使用します(特に、win32関数を呼び出したい場合-MarshalAsやその他の相互運用属性は恐ろしく、完全に理解できません)。

3
Eamon Nerbonne

破棄する前に、_Writernullではないことを確認する必要があります。 (nullになる可能性は低いようですが、念のために!)

protected virtual void Dispose(bool disposing)
{
    if (!_Disposed)
    {
        if (disposing && (_Writer != null))
        {
            _Writer.Dispose();
        }
        _Writer = null;
        _Disposed = true;
    }
}
1
LukeH

StreamWriterを開いた場合は、それも破棄する必要があります。そうしないと、リークが発生します。

0
Tal Pressman

私は他のコメントで述べられているすべてに同意しますが、それも指摘します。

  1. どちらの場合も、実際には_Writer = nullを設定する必要はありません。

  2. あなたがそうするつもりなら、それを処分する場所のifの中に置くのがおそらく最善です。おそらく大きな違いはありませんが、ファイナライザーによって破棄されるときに、管理対象オブジェクトで遊ぶことは一般的に想定されていません(他の人が指摘しているように、この場合は必要ありません)。

0
Yort