CA2202、このケースを解決する方法


102

次のコードからすべてのCA2202警告を削除する方法を誰かに教えてもらえますか?

public static byte[] Encrypt(string data, byte[] key, byte[] iv)
{
    using(MemoryStream memoryStream = new MemoryStream())
    {
        using (DESCryptoServiceProvider cryptograph = new DESCryptoServiceProvider())
        {
            using (CryptoStream cryptoStream = new CryptoStream(memoryStream, cryptograph.CreateEncryptor(key, iv), CryptoStreamMode.Write))
            {
                using(StreamWriter streamWriter = new StreamWriter(cryptoStream))
                {
                    streamWriter.Write(data);
                }
            }
        }
        return memoryStream.ToArray();
    }
}

警告7 CA2202:Microsoft.Usage:メソッド 'CryptoServices.Encrypt(string、byte []、byte [])'でオブジェクト 'cryptoStream'を複数回破棄できます。System.ObjectDisposedExceptionの生成を回避するために、オブジェクトに対してDisposeを複数回呼び出さないでください。:行:34

警告8 CA2202:Microsoft.Usage:メソッド 'CryptoServices.Encrypt(string、byte []、byte [])'でオブジェクト 'memoryStream'を複数回破棄できます。System.ObjectDisposedExceptionの生成を回避するには、オブジェクトに対してDisposeを複数回呼び出さないでください。:行:34、37

これらの警告を表示するにはVisual Studio Code Analysisが必要です(これらはC#コンパイラの警告ではありません)。


1
このコードはこれらの警告を生成しません。
Julien Hoarau

1
このため警告は表示されません(警告レベル4、VS2010)。そして、この領域で問題をググる誰かのために、警告のテキストも追加してください。
Henk Holterman、2009

29
CAxxxx警告は、コード分​​析とFxCop によって生成されます。
dtb

この警告は表示されているコードには適用されません-このシナリオでは警告を抑制できます。コードを確認してその評価に同意したら、これをメソッドの上に配置します: " [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification="BrainSlugs83 said so.")]" using System.Diagnostics.CodeAnalysis;-usingsブロックに" "ステートメントがあることを確認します。
BrainSlugs83 2013年

回答:


-3

これは警告なしでコンパイルされます:

    public static byte[] Encrypt(string data, byte[] key, byte[] iv)
    {
        MemoryStream memoryStream = null;
        DESCryptoServiceProvider cryptograph = null;
        CryptoStream cryptoStream = null;
        StreamWriter streamWriter = null;
        try
        {
            memoryStream = new MemoryStream();
            cryptograph = new DESCryptoServiceProvider();
            cryptoStream = new CryptoStream(memoryStream, cryptograph.CreateEncryptor(key, iv), CryptoStreamMode.Write);
            var result = memoryStream;              
            memoryStream = null;
            streamWriter = new StreamWriter(cryptoStream);
            cryptoStream = null;
            streamWriter.Write(data);
            return result.ToArray();
        }
        finally
        {
            if (memoryStream != null)
                memoryStream.Dispose();
            if (cryptograph != null)
                cryptograph.Dispose();
            if (cryptoStream != null)
                cryptoStream.Dispose();
            if (streamWriter != null)
                streamWriter.Dispose();
        }
    }

コメントに応じて編集:このコードでは警告が生成されないが、元のコードでは生成されることを再度確認しました。元のコードでは、CryptoStream.Dispose()およびMemoryStream().Dispose()は実際には2回呼び出されます(問題になる場合とそうでない場合があります)。

変更されたコードは次のように機能します。null破棄の責任が別のオブジェクトに移されるとすぐに、参照はに設定されます。たとえば、コンストラクタの呼び出しが成功memoryStreamしたnull後にが設定されCryptoStreamます。コンストラクタへの呼び出しが成功した後、cryptoStreamはに設定されます。例外が発生しない場合は、ブロックに配置され、次に破棄されます。nullStreamWriterstreamWriterfinallyCryptoStreamMemoryStream


85
-1 抑制すべき警告に準拠するためだけに醜いコードを作成するのは本当に悪いことです。
ジョルダン2011

4
将来のある時点で修正されてしまう可能性のある何かをコーディングしたり、抑制したりするべきではないことに同意します。
peteski

3
これはどのように問題を解決しますか?CA2202は引き続き報告されます。これは、finallyブロックでmemoryStreamを2回破棄できるためです。
クリスゲスラー

3
CryptoStreamは内部的にMemoryStreamのDisposeを呼び出すため、2回呼び出される可能性があり、これが警告の理由です。私はあなたの解決策を試しましたが、それでも警告が表示されます。
クリスゲスラー

2
ああ、そうです、あなたは正しいです-クリーンアップロジックが混在しているとは思いませんでした...ロジックロジック...-奇妙で不可解なだけです-確かに賢いです-しかし、再び、怖い-本番用コードではこれを行わないでください。明確にする必要があります。これで解決できる実際の機能上の問題はないことがわかりますよね?(これらのオブジェクトを複数回破棄しても問題ありません。)-可能であれば、反対票を削除します(SOが私を防ぎます。回答を編集する必要があると言っています)-しかし、私はしぶしぶそうするだけです--真剣に、これを行わないでください。
BrainSlugs83 2013

142

この場合、警告を抑制する必要があります。使い捨てを扱うコードは一貫している必要があり、他のクラスが作成した使い捨ての所有権を取得し、Disposeそれらを呼び出すことを気にする必要はありません。

[SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
public static byte[] Encrypt(string data, byte[] key, byte[] iv) {
  using (var memoryStream = new MemoryStream()) {
    using (var cryptograph = new DESCryptoServiceProvider())
    using (var cryptoStream = new CryptoStream(memoryStream, cryptograph.CreateEncryptor(key, iv), CryptoStreamMode.Write))
    using (var streamWriter = new StreamWriter(cryptoStream)) {
      streamWriter.Write(data);
    }
    return memoryStream.ToArray();
  }
}

UPDATE:IDisposable.Disposeのドキュメントあなたはこれを読むことができます:

オブジェクトのDisposeメソッドが2回以上呼び出される場合、オブジェクトは、最初の呼び出し以降のすべての呼び出しを無視する必要があります。Disposeメソッドが複数回呼び出された場合、オブジェクトは例外をスローしてはなりません。

このルールは、using上で示したように、使い捨てのカスケードで開発者がステートメントを正しく使用できるようにするために存在すると主張できます(または、おそらくこれは素晴らしい副作用です)。同様に、CA2202は有用な目的を果たさないため、プロジェクトごとに抑制する必要があります。実際の原因はの不完全な実装でDisposeあり、CA1065がそれを処理する必要があります(責任がある場合)。


14
私の意見では、これはfxcopのバグです。このルールは単に間違っています。disposeメソッドがObjectDisposedExceptionをスローすることはありません。スローする場合は、この方法で、disposeを実装するコードの作成者にバグを報告して対処してください。
justin.m.chase 2014

14
私は他のスレッドの@HansPassantに同意します。ツールはその役割を果たしており、クラスの予期しない実装の詳細を警告しています。個人的には、API自体の設計が本当の問題だと思います。ネストされたクラスがデフォルトで他の場所で作成された別のオブジェクトの所有権を取得しても問題がないと想定することは、非常に疑わしいようです。結果のオブジェクトが返される場合に便利な場所を確認できますが、その仮定のデフォルト設定は直観に反しており、通常のIDisposableパターンに違反しています。
BTJ 2014

8
ただし、msdnは、このタイプのメッセージを抑制することを推奨しません。見ている:msdn.microsoft.com/en-us/library/...を
アディルMammadov

2
リンク@AdilMammadovをお寄せいただきありがとうございます。役立つ情報ですが、マイクロソフトはこれらのことについて常に正しいとは限りません。
Tim Abell 2017年

40

まあ、それは正確です。これらのストリームのDispose()メソッドは複数回呼び出されます。StreamReaderクラスは、cryptoStreamの「所有権」を取得するため、streamWriterを破棄すると、cryptoStreamも破棄されます。同様に、CryptoStreamクラスは、memoryStreamの責任を引き継ぎます。

これらは、実際のバグではありません。これらの.NETクラスは、複数のDispose()呼び出しに対して耐性があります。ただし、警告を取り除きたい場合は、これらのオブジェクトのusingステートメントを削除する必要があります。そして、コードが例外をスローした場合にどうなるかを推論するときは、少し苦痛を覚えます。または、警告を属性でシャットダウンします。またはそれはばかげているので警告を無視してください。


10
クラスの内部動作に関する特別な知識(使い捨てが別のものの所有権を取得するなど)を持っていることは、再利用可能なAPIを設計したいかどうかを尋ねるには多すぎます。だから私はそのusing声明は留まるべきだと思う。これらの警告は本当にばかげています。
2010

4
@Jordão-それはツールの目的ではありませんか?あなたが知らなかったかもしれない内部の行動についてあなたに警告するために?
Hans Passant

8
同意する。しかし、私はまだusing声明を落とさないでしょう。別のオブジェクトに依存して、自分が作成したオブジェクトを破棄するのは間違っていると感じているだけです。このコードでは、それはOKですが、そこにいるの多くの実装StreamTextWriterそこに(BCL上だけではなく)。それらをすべて使用するコードは一貫している必要があります。
ジョルダン

3
はい、ヨルダンに同意します。プログラマにAPIの内部動作を認識させたい場合は、関数にDoSomethingAndDisposeStream(Stream stream、OtherData data)と名前を付けて説明します。
ZZZ 2013

4
@HansPassant 指定されたパラメーターでXmlDocument.Save()メソッドが呼び出さDisposeれることが文書化されている場所を指摘できますか?Save(XmlWriter)(FxCopバグが発生しているSave()場合)のドキュメント、メソッド自体、またはドキュメント自体には表示されませんXmlDocument
Ian Boyd

9

ときのStreamWriterが配置されて、それが自動的に包まれ処分されますストリーム(:ここCryptoStreamを)。CryptoStreamは、ラップされたStream(ここでは、MemoryStream)も自動的に破棄します。

したがって、MemoryStreamCryptoStreamusingステートメントの両方によって破棄されます。そして、CryptoStreamは、StreamWriterと外部のusingステートメントによって破棄されます。


いくつかの実験の後、警告を完全に取り除くことは不可能のようです。理論的には、MemoryStreamを破棄する必要がありますが、その後、理論的にはそのToArrayメソッドにアクセスできなくなりました。実際には、MemoryStreamを破棄する必要がないので、このソリューションを使用してCA2000警告を抑制します。

var memoryStream = new MemoryStream();

using (var cryptograph = new DESCryptoServiceProvider())
using (var writer = new StreamWriter(new CryptoStream(memoryStream, ...)))
{
    writer.Write(data);
}

return memoryStream.ToArray();

9

私はを使用してこれを行い#pragma warning disableます。

.NET Frameworkガイドラインでは、IDisposable.Disposeを複数回呼び出すことができるように実装することを推奨しています。IDisposable.DisposeのMSDNの説明から:

Disposeメソッドが複数回呼び出された場合、オブジェクトは例外をスローしてはなりません

したがって、警告はほとんど意味がないようです:

System.ObjectDisposedExceptionの生成を回避するには、オブジェクトでDisposeを複数回呼び出さないでください。

標準の実装ガイドラインに準拠していない、不適切に実装されたIDisposableオブジェクトを使用している場合、警告が役立つ可能性があると思われるかもしれません。しかし、あなたがしているように.NET Frameworkのクラスを使用するときは、#pragmaを使用して警告を抑制しても安全だと思います。そして、私見これは、この警告についてのMSDNドキュメントで提案されているように、フープを通過するよりも好ましいです。


4
CA2202はコード分析の警告であり、コンパイラの警告ではありません。#pragma warning disableコンパイラの警告を抑制するためにのみ使用できます。コード分​​析の警告を抑制するには、属性を使用する必要があります。
Martin Liversage 2015年

2

私のコードでも同様の問題に直面していました。

CA2202全体がトリガーされるように見えます MemoryStreamコンストラクター(CA2000)で例外が発生した場合にできるされるように見えます。

これは次のように解決できます:

 1 public static byte[] Encrypt(string data, byte[] key, byte[] iv)
 2 {
 3    MemoryStream memoryStream = GetMemoryStream();
 4    using (DESCryptoServiceProvider cryptograph = new DESCryptoServiceProvider())
 5    {
 6        CryptoStream cryptoStream = new CryptoStream(memoryStream, cryptograph.CreateEncryptor(key, iv), CryptoStreamMode.Write);
 7        using (StreamWriter streamWriter = new StreamWriter(cryptoStream))
 8        {
 9            streamWriter.Write(data);
10            return memoryStream.ToArray();
11        }
12    }
13 }
14
15 /// <summary>
16 /// Gets the memory stream.
17 /// </summary>
18 /// <returns>A new memory stream</returns>
19 private static MemoryStream GetMemoryStream()
20 {
21     MemoryStream stream;
22     MemoryStream tempStream = null;
23     try
24     {
25         tempStream = new MemoryStream();
26
27         stream = tempStream;
28         tempStream = null;
29     }
30     finally
31     {
32         if (tempStream != null)
33             tempStream.Dispose();
34     }
35     return stream;
36 }

memoryStream最後の内部を返す必要があることに注意してくださいusingステートメント(行10)cryptoStreamstreamWriter usingステートメントで使用されるため、行11で破棄されるため、行11 memoryStreamでも破棄されるためmemoryStreamです(これは、cryptoStream)。

少なくともこのコードは私にとってはうまくいきました。

編集:

おかしいかもしれませんが、私はあなたが GetMemoryStreamメソッドを次のコードにと、

/// <summary>
/// Gets a memory stream.
/// </summary>
/// <returns>A new memory stream</returns>
private static MemoryStream GetMemoryStream()
{
    return new MemoryStream();
}

同じ結果が得られます。


1

暗号ストリームは、メモリストリームに基づいています。

起こっているように見えるのは、(使用の終わりに)クリポストリームが破棄されると、メモリストリームも破棄され、その後、メモリストリームが再び破棄されることです。


1

私はこれを正しい方法で解決したかった-それは警告を抑制せずに、すべての使い捨てオブジェクトを正しく処分せずに。

3つのストリームのうち2つをフィールドとして取り出しDispose()、クラスのメソッドに配置しました。はい、IDisposableインターフェイスを実装することは必ずしもあなたが探しているものではないかもしれませんが、ソリューションdispose()はコード内のすべてのランダムな場所からの呼び出しに比べてかなりきれいに見えます。

public class SomeEncryption : IDisposable
    {
        private MemoryStream memoryStream;

        private CryptoStream cryptoStream;

        public static byte[] Encrypt(string data, byte[] key, byte[] iv)
        {
             // Do something
             this.memoryStream = new MemoryStream();
             this.cryptoStream = new CryptoStream(this.memoryStream, encryptor, CryptoStreamMode.Write);
             using (var streamWriter = new StreamWriter(this.cryptoStream))
             {
                 streamWriter.Write(plaintext);
             }
            return memoryStream.ToArray();
        }

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

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

                if (this.cryptoStream != null)
                {
                    this.cryptoStream.Dispose();
                }
            }
        }
   }

0

トピック外ですが、グループ化に別のフォーマット手法を使用することをお勧めしますusing

using (var memoryStream = new MemoryStream())
{
    using (var cryptograph = new DESCryptoServiceProvider())
    using (var encryptor = cryptograph.CreateEncryptor(key, iv))
    using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
    using (var streamWriter = new StreamWriter(cryptoStream))
    {
        streamWriter.Write(data);
    }

    return memoryStream.ToArray();
}

私はまた、 var本当に長いクラス名の繰り返しを避けるために、ここでsます。

PS指摘してくれた@ShellShockのおかげで、範囲外のステートメントで作成されるusingためmemoryStream、最初に中括弧を省略できませんreturn


5
memoryStream.ToArray()はスコープ外になりませんか?
ポリファン2010

これは、元のコードとまったく同じです。中かっこは省略しました。sでできるのと同じですif(ただし、usings 以外の場合は、この手法はお勧めしません)。
Dan Abramov、2009

2
元のコードでは、memoryStream.ToArray()は最初のusingのスコープ内にありました。範囲外です。
Polyfun

どうもありがとうございましたreturn。仰るとおり。これを反映するように回答を編集しました。
Dan Abramov、

個人的には、using中括弧がないとコードが壊れやすくなると思います(何年ものdiffとmergeを考えてみてください)。joelonsoftware.com/2005/05/11/making-wrong-code-look-wrongimperialviolet.org/2014/02/22/applebug.html
ティム・アベル

0

すべての使用を避け、ネストされたDispose-Callsを使用してください!

    public static byte[] Encrypt(string data, byte[] key, byte[] iv)
    {
        MemoryStream memoryStream = null;
        DESCryptoServiceProvider cryptograph = null;
        CryptoStream cryptoStream = null;
        StreamWriter streamWriter = null;

        try
        {
            memoryStream = new MemoryStream();
            cryptograph = new DESCryptoServiceProvider();
            cryptoStream = new CryptoStream(memoryStream, cryptograph.CreateEncryptor(key, iv), CryptoStreamMode.Write);
            streamWriter = new StreamWriter(cryptoStream);

            streamWriter.Write(data);
            return memoryStream.ToArray();
        }
        finally 
        {
            if(streamWriter != null)
                streamWriter.Dispose();
            else if(cryptoStream != null)
                cryptoStream.Dispose();
            else if(memoryStream != null)
                memoryStream.Dispose();

            if (cryptograph != null)
                cryptograph.Dispose();
        }
    }

1
usingこの場合、避けるべき理由を説明してください。
StuperUser 2012年

1
使用ステートメントを途中で保持することもできますが、他のステートメントを解決する必要があります。論理的で一貫性のあるアップグレード可能なソリューションを取得するために、すべての使用法を削除することにしました!
Harry Saltzman、

0

Disposeオブジェクトの複数の呼び出しを確認できるように、コードをラップ解除したかっただけです。

memoryStream = new MemoryStream()
cryptograph = new DESCryptoServiceProvider()
cryptoStream = new CryptoStream()
streamWriter = new StreamWriter()

memoryStream.Dispose(); //implicitly owned by cryptoStream
cryptoStream.Dispose(); //implicitly owned by streamWriter
streamWriter.Dispose(); //through a using

cryptoStream.Dispose(); //INVALID: second dispose through using
cryptograph.Dispose(); //through a using
memorySTream.Dipose(); //INVALID: second dispose through a using

return memoryStream.ToArray(); //INVALID: accessing disposed memoryStream

ほとんどの.NETクラスは(願わくば)への複数の呼び出しのミスに対して回復力がありますが.Disposeすべてのクラスがプログラマーの誤用に対する防御力があるわけではありません。

FX Copはこれを知っており、警告します。

いくつかの選択肢があります。

  • Dispose任意のオブジェクトで1回だけ呼び出します。使わないusing
  • disposeを2度呼び出し続け、コードがクラッシュしないことを願って
  • 警告を抑制

-1

ストリームを使用せずにbyte []を受け取り、byte []を返すこの種のコードを使用しました

public static byte[] Encrypt(byte[] data, byte[] key, byte[] iv)
{
  DES des = new DES();
  des.BlockSize = 128;
  des.Mode = CipherMode.CBC;
  des.Padding = PaddingMode.Zeros;
  des.IV = IV
  des.Key = key
  ICryptoTransform encryptor = des.CreateEncryptor();

  //and finaly operations on bytes[] insted of streams
  return encryptor.TransformFinalBlock(plaintextarray,0,plaintextarray.Length);
}

このようにすれば、エンコーディングを使用して文字列からbyte []に​​変換するだけです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.