7

Visual Studio 2010 (主に C# 4.0) 開発標準の一部として、コード分析が有効になっています。最近提出された新しいプロジェクトのコードをレビューしていると、大量のコードが表示されます。

CA2000 : Microsoft.Reliability: メソッド 'XYZ' で、オブジェクト 'ABC' がすべての例外パスで破棄されていません。オブジェクト 'ABC' へのすべての参照が範囲外になる前に、オブジェクト 'ABC' で System.IDisposable.Dispose を呼び出します。

警告。問題は、私が何をしても警告が消えるように見えないことです。私は何時間もかけて Web を精査し、できる限りのことを試しました。

最初に、ローカル変数を適切に破棄するために単純な using ブロックを入れることについて話しているのではないことを明確にさせてください。それは問題ではありません。私の場合、これらの警告は、オブジェクトがメソッドによって返されるか、メソッド内の別のオブジェクトに割り当てられたときに表示されます。

このような警告を 4 つ含むコード サンプルを次に示します。

public void MainMethod()
{
    var object1 = CreateFirstObject();    // Warning here
    var object2 = CreateSecondObject();   // Warning here

    SomeCollectionProperty.Add(object1);
    SomeCollectionProperty.Add(object2);
}

private SomeObject CreateFirstObject()
{
    var theObject = new SomeObject()      // Warning here
    {
        FirstProperty = "some value",
        // ...
    };

    return theObject;
}

private SomeOtherObject CreateSecondObject()
{
    var theObject = new SomeOtherObject() // Warning here
    {
        FirstProperty = "a different value",
        // ...
    };

    return theObject;
}

警告が発生する行にコメントしました。

MSDN の記事 (こちら)で説明されているように、両方の Create メソッドをリファクタリングしようとしましたが、それでも警告が表示されます。

更新 SomeObject と SomeOtherObject の両方が IDisposable を実装していることに注意してください。

また、オブジェクト初期化子が問題の構成要素である可能性がありますが、初期化子は 2 つのプライベート メソッドに分離されており、MainMethod の警告とは関係がないことに注意してください。

これらのメソッドを適切に実装して CA2000 の警告を排除する方法を誰か教えてもらえますか?

4

5 に答える 5

10

この場合、CA2000 によって検出されている問題は、メソッドから渡される前に例外が発生した場合、破棄可能なインスタンスが「孤立」する可能性があることです。たとえば、CreateFirstObject の「正しい」実装は次のようになります。

private SomeObject CreateFirstObject()
{
    var theObject = new SomeObject();
    try
    {
        theObject.FirstProperty = "some value";
    }
    catch
    {
        theObject.Dispose();
        throw;
    }

    return theObject;
}

MainMethod の望ましい動作について説明したことを考えると、その「正しい」実装は次のようになります。

public void MainMethod()
{
    var object1 = CreateFirstObject();
    try
    {
        SomeCollectionProperty.Add(object1);

        var object2 = CreateSecondObject();
        try
        {
            SomeCollectionProperty.Add(object2);
        }
        catch
        {
            object2.Dispose();
            throw;
        }
    }
    catch
    {
        object1.Dispose();
        SomeCollectionProperty.Remove(object1); // Not supposed to throw if item does not exist in collection.

        throw;
    }
}
于 2011-11-23T16:30:27.803 に答える
1

警告を取り除く 1 つの方法は、コードで警告を抑制することです。

[SuppressMessage(
    "Microsoft.Reliability",
    "CA2000:DisposeObjectsBeforeLosingScope",
    Justification = "Factory method")]

しかし、これは問題の真の解決策ではありません。

解決策は次のとおりです:所有権が譲渡されたときに CA2000 の警告を取り除くには?

上記のリンクでは、基本的に を実装するコレクションにオブジェクトを追加するように述べられていますICollection<T>が、私はそれをテストしていません。

于 2011-11-23T16:23:59.407 に答える
0

返されたオブジェクトをmainでusingブロックにラップするか、finallyを実装してオブジェクトを破棄するとどうなりますか?

SomeOtherObjectsはIDisposableを実装する必要がありますか?

于 2011-11-23T16:02:29.843 に答える
0

必要なのは、「使用」ブロックに似たパターンを実装することですが、オブジェクトが正常に返されるシナリオではオブジェクトの破棄を無効にします。Nicole Calinoiu によって提供されたアプローチは合理的ですが、単純にバブルアップする例外をキャッチすることは避けたいと思います。C# 言語の制約を考えると、コードの私の好みの表現は、InitializedSuccessfully フラグを使用し、InitializedSuccessfully が呼び出されていない場合に処理を行う "finally" ブロックを使用することです。

クラスに多くの IDIsposable オブジェクトが含まれる予定であり、構築が完了するとそのようなオブジェクトのセットが修正される場合、IDisposable オブジェクトのリストを保持する IDisposable マネージャー クラスを定義すると便利な場合があります。クラスのコンストラクターに DisposableManager オブジェクトをパラメーターとして受け入れさせ、構築するすべてのオブジェクトをそれによって生成されたリストに配置します (クラスにインスタンス メソッドがあると便利な場合があります。

T regDisposable<T>RegDispose(T newThing) ここで、T:IDisposable
{
  myDisposableManager.Add(newThing);
  newThing を返します。
}

これを使用するには、myDisposableManager が初期化された後に、次のように言うだけですvar someDisposableField = RegDispose(new someDisposableType());。このようなパターンには、次の 2 つの大きな利点があります。

  1. メイン クラスがその Dispose 実装で行う必要があるのは、次のとおりです。`if (myDisposableManager != null) myDisposableManager.Dispose();` コンストラクターで IDisposable オブジェクトを設定する操作 (RegDispose を使用) は、そのクリーンアップも提供します。
  2. メイン オブジェクトのコンストラクターを呼び出すコードは、コンストラクターが例外をスローした場合、作成して渡した DisposableManager オブジェクトで Dispose メソッドを呼び出すことができます。これにより、部分的に作成されたオブジェクトのタイムリーなクリーンアップが保証されます。しかし、不可能です。

vb では、基本クラスのコンストラクターがコンストラクター パラメーターを、フィールド初期化子で使用できるフィールドとして公開することができます。したがって、明示的なコンストラクターだけでなく、フィールド初期化子でも RegDispose パターンを適切に使用できます。C# では、それは不可能です。その目的で [threadstatic] フィールドを使用することは可能ですが、設定されたそのようなフィールドが設定解除されることを確実にするために、いくつかの注意が必要です。コンストラクターは、スレッドプール スレッドのようなものから呼び出され、メモリ リークが発生する可能性があります。また、threadstatic フィールドには、通常のフィールドほど効率的にアクセスすることはできません。登録された IDisposable オブジェクトごとに 1 回ずつ、thread-static フィールドを何度も再フェッチする必要を回避する C# の方法がわかりません。

于 2011-11-23T18:13:52.767 に答える