15

私は、正常に動作し、実行時に奇妙なことを示さないプロジェクトをかなりコーディングしました。そこで、静的コード分析ツールを実行することにしました(Visual Studio 2010を使用しています)。ルールCA2000に違反していることが判明しました。メッセージは次のとおりです。

警告-CA2000:Microsoft.Reliability:メソッド'Bar.getDefaultFoo()'で、オブジェクト' new Foo()'へのすべての参照がスコープ外になる前に、System.IDisposable.Disposeを呼び出します。

参照されるコードは次のようになります。

private static IFoo getDefaultFoo()
{
    return (Baz.canIDoIt()) ? new Foo() : null;
}

私は自分自身を考えました:多分条件式はロジック(私のものまたはバリデーターのもの)を台無しにします。これに変更されました:

private static IFoo getDefaultFoo()
{
    IFoo ret = null;
    if (Baz.canIDoIt())
    {
        retFoo = new Foo();
    }
    return ret;
}

同じことが再び起こりましたが、オブジェクトは。と呼ばれるようになりましたretFoo。私はグーグルで検索し、msdnを実行し、stackoverflowを実行しました。この記事を見つけました。オブジェクトの作成後に実行する必要のある操作はありません。私はそれへの参照を返す必要があるだけです。ただし、OpenPort2の例で提案されているパターンを適用しようとしました。これで、コードは次のようになります。

private static IFoo getDefaultFoo()
{
    Foo tempFoo = null;
    Foo retFoo = null;
    try
    {
        if (Baz.canIDoIt())
        {
            tempFoo = new Foo();
        }
        retFoo= tempFoo;
        tempFoo = null;
    }
    finally
    {
        if (tempFoo != null)
        {
            tempFoo.Dispose();
        }
    }
    return retFoo;
}

もう一度同じメッセージですが、tempFoo今回は変数がルール違反です。つまり、基本的に、コードはねじれ、長く、不合理で、不必要に複雑になり、まったく同じように動作しますが、速度は遅くなります。

同じルールが同様の方法で有効なコードを攻撃しているように見えるこの質問も見つけました。そして、質問者は警告を無視するようにアドバイスされています。私もこのスレッドと同様の質問の塊を読みました。

見逃したことはありますか?ルールにバグがありますか/無関係ですか?私は何をすべきか?無視?魔法のように扱いますか?たぶん、いくつかのデザインパターンを適用しますか?

編集:

ニコールのリクエストに応じて、関連するコード全体を、私も使用してみたフォームで送信しています。

public class DisposableFooTest
{
    public interface IFoo
    {
        void bar();
    }

    public class Foo : IFoo, IDisposable
    {
        public void bar()
        {
            Console.Out.WriteLine("Foo baring now");
        }

        public void Dispose()
        {
            // actual Dispose implementation is irrelevant, or maybe it is?
            // anyway I followed microsoft dispose pattern
            // with Dispose(bool disposing)
        }
    }

    public static class Baz
    {
        private static bool toggle = false;
        public static bool canIDoIt()
        {
            toggle ^= true;
            return toggle;
        }
    }

    private static IFoo getDefaultFoo()
    {
        IFoo result = null;
        try
        {
            if (Baz.canIDoIt())
            {
                result = new Foo();
            }

            return result;
        }
        catch
        {
            if (result != null)
            {
                (result as IDisposable).Dispose();
                // IFoo does not inherit from IDisposable, hence the cast
            }

            throw;
        }
    }

    public static void Main()
    {
        IFoo bar = getDefaultFoo();
    }
}

分析レポートには、次の内容が含まれています。
`CA2000:Microsoft.Reliability:メソッド'DisposableFooTest.getDefaultFoo()'で、System.IDisposable.Dispose on object'result'を呼び出してから、オブジェクトへのすべての参照がスコープ外になります。%% projectpath %% \DisposableFooTest.cs44テスト

Edit2:

次のアプローチでCA2000の問題が解決しました。

private static IFoo getDefaultFoo()
{
    Foo result = null;
    try
    {
        if (Baz.canIDoIt())
        {
            result = new Foo();
        }
        return result;
    }
    finally
    {
        if (result != null)
        {
            result.Dispose();
        }
    }
}

残念ながら、私はそのように行くことはできません。さらに、オブジェクト指向の原則、グッドプラクティス、およびガイドラインに従ってコードを簡素化し、読み取り可能、保守可能、および拡張可能にすることを期待しています。誰かが意図したとおりに読んだのではないかと思います。可能であればFooを与えるか、そうでない場合はnullにします。

4

4 に答える 4

10

これは誤検知の警告です。が実装されているIFoo場合、コード分析ツールが適切に破棄していないことを警告しない限り、の適切なインスタンスを返す方法はありません。IFooIDisposable

コード分​​析は、意図やロジックを分析するのではなく、一般的なエラーについて警告しようとするだけです。IDisposableこの場合、オブジェクトを使用していて、を呼び出していないように見えますDispose()。ここでは、メソッドが新しいインスタンスを返し、ファクトリメソッドの形式として機能するようにするため、設計によりこれを行っています。

于 2013-01-24T00:42:48.053 に答える
8

ここでの問題は、C# コードが明示的に行っていることではなく、生成された IL が複数の命令を使用して、C# コードの 1 つの "ステップ" のように見えることを実行していることです。

たとえば、3 番目のバージョン (try/finally を使用) では、return retFoo実際には、コンパイラによって生成され、元のコードでは確認できない追加の変数の割り当てが行に含まれています。この割り当ては try/finally ブロックの外側で行われるため、未処理の例外が発生して使い捨ての Foo インスタンスが「孤立」したままになる可能性が実際にあります。

潜在的な問題を回避する (そして CA2000 スクリーニングに合格する) バージョンを次に示します。

private static IFoo getDefaultFoo()
{
    IFoo result = null;
    try
    {
        if (Baz.canIDoIt())
        {
            result = new Foo();
        }

        return result;
    }
    catch
    {
        if (result != null)
        {
            result.Dispose();
        }

        throw;
    }
}

明らかに、それは最初のバージョンと比較して可読性と保守性をかなり妨げます。そのため、孤児になる可能性と孤児を破棄しないことの結果が実際にコードの変更に値するかどうか、または CA2000 違反を抑制するかどうかを決定する必要があります。この特定のケースでは好ましいかもしれません。

于 2013-01-24T13:40:06.933 に答える
2

静的分析は、基本的に次の理由で不平を言っています。

  1. インターフェイスは から継承されIFooません。IDisposable
  2. ただし、破棄する必要がある具体的な実装を返しているため、呼び出し元はこれを知る方法がありません。

つまり、GetDefaultFooメソッドの呼び出し元は実装を期待しIFooていますが、実装を明示的に破棄する必要があることを認識していないため、おそらく実装を破棄しません。これが他のライブラリで一般的な方法である場合、アイテムが実装されているかどうか、IDisposable可能なインターフェイスのすべての実装を手動で確認する必要があります。

明らかに、それには何か問題があります。

私見、これを修正する最もクリーンな方法は、IFoo継承元を作成することIDisposableです。

public interface IFoo : IDisposable
{
    void Bar();
}

このようにして、呼び出し元は、受信した可能性のある実装を破棄する必要があることを認識します。また、これにより、静的アナライザーは、オブジェクトを使用しているすべての場所をチェックし、IFooそれらを適切に破棄していない場合に警告することもできます。

于 2013-01-25T13:20:16.667 に答える