2

私はボイラープレート コードのファンではありません。コピーして貼り付けて再利用すると、エラーが発生しやすくなります。コード スニペットやスマート テンプレートを使用したとしても、他の開発者が正しく行ったという保証はありません。そして、コードを見る必要がある場合は、それを理解したり、維持したりする必要があります。

コミュニティから知りたいのは、クラス階層のIDisposeの実装は、 「従来の」破棄パターンの正当な代替手段ですか? 正当とは、正しく、適度にパフォーマンスが高く、堅牢で、保守可能であることを意味します。

この代替案が明らかに間違っていても問題ありませんが、そうである場合は、その理由を知りたいです。

この実装は、クラス階層を完全に制御できることを前提としています。そうしないと、定型コードに頼らざるを得なくなるでしょう。Add*()の呼び出しは通常、コンストラクターで行われます。

public abstract class DisposableObject : IDisposable
{
  protected DisposableObject()
  {}

  protected DisposableObject(Action managedDisposer)
  {
     AddDisposers(managedDisposer, null);
  }

  protected DisposableObject(Action managedDisposer, Action unmanagedDisposer)
  {
     AddDisposers(managedDisposer, unmanagedDisposer);
  }

  public bool IsDisposed
  {
     get { return disposeIndex == -1; }
  }

  public void CheckDisposed()
  {
     if (IsDisposed)
        throw new ObjectDisposedException("This instance is disposed.");
  }

  protected void AddDisposers(Action managedDisposer, Action unmanagedDisposer)
  {
     managedDisposers.Add(managedDisposer);
     unmanagedDisposers.Add(unmanagedDisposer);
     disposeIndex++;
  }

  protected void AddManagedDisposer(Action managedDisposer)
  {
     AddDisposers(managedDisposer, null);
  }

  protected void AddUnmanagedDisposer(Action unmanagedDisposer)
  {
     AddDisposers(null, unmanagedDisposer);
  }

  public void Dispose()
  {
     if (disposeIndex != -1)
     {
        Dispose(true);
        GC.SuppressFinalize(this);
     }
  }

  ~DisposableObject()
  {
     if (disposeIndex != -1)
        Dispose(false);
  }

  private void Dispose(bool disposing)
  {
     for (; disposeIndex != -1; --disposeIndex)
     {
        if (disposing)
           if (managedDisposers[disposeIndex] != null)
              managedDisposers[disposeIndex]();
        if (unmanagedDisposers[disposeIndex] != null)
           unmanagedDisposers[disposeIndex]();
     }
  }

  private readonly IList<Action> managedDisposers = new List<Action>();
  private readonly IList<Action> unmanagedDisposers = new List<Action>();
  private int disposeIndex = -1;
}

これは、ファイナライズ (ほとんどの実装ではファイナライザーが必要ないことを知っている)、オブジェクトが破棄されているかどうかのチェックなどのサポートを提供するという意味で、「完全な」実装です。たとえば、実際の実装では、ファイナライザーを削除したり、ファイナライザーを含むDisposableObjectのサブクラス。基本的に、この質問のためだけに考えられるすべてを投入しました。

おそらく、私が見逃したエッジケースや難解な状況がいくつかあるので、このアプローチに穴を開けるか、修正して補強するために誰かを招待します.

他の方法として、DisposableObject で 2 つのリストの代わりに単一の Queue<Disposer> ディスポーザーを使用することもできますこの場合、ディスポーザーが呼び出されると、リストから削除されます。他にも考えられるわずかなバリエーションがありますが、一般的な結果は同じで、ボイラープレート コードはありません。

4

3 に答える 3

5

最初に遭遇する可能性のある問題は、C# では 1 つの基本クラス (この場合は常にDisposableObject. ここで、継承する必要があるクラスDisposableObjectや他のオブジェクトが継承できるように、追加のレイヤーを強制してクラス階層を乱雑にしています。

また、この実装により、将来的に多くのオーバーヘッドとメンテナンスの問題が発生します (新しい人がプロジェクトに参加するたびに繰り返されるトレーニングコストは言うまでもなく、定義されたパターンではなく、この実装をどのように使用する必要があるかを説明する必要があります)。 . 2 つのリストで追跡する複数の状態があることがわかっています。アクションの呼び出しに関するエラー処理はありません。アクションを呼び出すときの構文は「奇妙」に見えます (配列からメソッドを呼び出すことは一般的かもしれませんが、配列アクセスの後に () を置くだけの構文は奇妙に見えます)。

書かなければならないボイラープレートの量を減らしたいという気持ちは理解できますが、使い捨て性は一般的に、近道をしたり、パターンから逸脱したりすることをお勧めする分野の 1 つではありません。私が通常得る最も近い方法はDispose()、特定のオブジェクトの実際の呼び出しをラップするヘルパー メソッド (または拡張メソッド) を使用することです。これらの呼び出しは通常、次のようになります。

if (someObject != null)
{
   someObject.Dispose();
}

これはヘルパー メソッドを使用して簡略化できますが、FxCop (または、適切な Dispose 実装をチェックするその他の静的分析ツール) がエラーを出すことに注意してください。

パフォーマンスに関する限り、このタイプの実装では多くのデリゲート呼び出しを行っていることに注意してください。これは、デリゲートの性質上、通常のメソッド呼び出しよりも多少コストがかかります。

保守性は間違いなくここでの問題です。前述したように、新しい人がプロジェクトに参加するたびにトレーニング コストが発生し、定義されたパターンではなく、この実装をどのように使用する必要があるかを説明する必要があります。それだけでなく、誰もが自分の使い捨てオブジェクトをリストに追加することを覚えているという問題があります。

全体として、これを行うのは悪い考えだと思います。特にプロジェクトとチームのサイズが大きくなるにつれて、多くの問題が発生するでしょう。

于 2009-09-09T02:11:36.317 に答える
2

一度に複数の開いているファイルやその他のリソースを追跡する必要がある場合があります。その際、次のようなユーティリティ クラスを使用します。その後、オブジェクトは引き続き Displose() を実装しますが、複数のリスト (マネージド/アンマネージド) を追跡することは開発者にとって簡単で明らかです。さらに、List オブジェクトからの派生は偶然ではなく、必要に応じて Remove(obj) を呼び出すことができます。私のコンストラクタは通常次のようになります。

        _resources = new DisposableList<IDisposable>();
        _file = _resources.BeginUsing(File.Open(...));

そして、ここにクラスがあります:

    class DisposableList<T> : List<T>, IDisposable
        where T : IDisposable
    {
        public bool IsDisposed = false;
        public T BeginUsing(T item) { base.Add(item); return item; }
        public void Dispose()
        {
            for (int ix = this.Count - 1; ix >= 0; ix--)
            {
                try { this[ix].Dispose(); }
                catch (Exception e) { Logger.LogError(e); }
                this.RemoveAt(ix);
            }
            IsDisposed = true;
        }
    }
于 2009-09-09T02:21:40.397 に答える
0

csharptest の回答の一般的なパターンが気に入っています。処分を中心に設計された基本クラスを持つことは少し制限されますが、vb.net を使用している場合、またはスレッド静的変数を使用するゲームを気にしない場合は、特別に設計された基本クラスを使用して、処分用の変数を登録することもできます。それらがフィールド初期化子または派生クラス コンストラクターで作成されたとき (通常、フィールド初期化子で例外がスローされた場合、既に割り当てられている IDisposable フィールドを破棄する方法はなく、派生クラスのコンストラクターが例外をスローした場合は、部分的に作成された基本オブジェクトがそれ自体を破棄する方法はありません)。

ただし、管理されていないリソースのリストは気にしません。ファイナライザーを持つクラスは、ファイナライズに必要のないオブジェクトへの参照を保持しないでください。代わりに、ファイナライズに必要なものは独自のクラスに配置する必要があり、「メイン」クラスは後者のクラスのインスタンスを作成し、それへの参照を保持する必要があります。

于 2011-01-04T20:34:28.977 に答える