web-dev-qa-db-ja.com

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

次のコードからすべての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:オブジェクト 'cryptoStream'は、メソッド 'CryptoServices.Encrypt(string、byte []、byte [])'で複数回破棄できます。 System.ObjectDisposedExceptionの生成を回避するには、オブジェクトに対してDisposeを複数回呼び出さないでください。:行:34

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

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

100
testalino

この場合、警告を抑制する必要があります。ディスポーザブルを扱うコードは一貫している必要があり、作成したディスポーザブルの所有権を他のクラスが取得し、それらに対して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メソッドが複数回呼び出された場合、オブジェクトは最初の呼び出し以降のすべての呼び出しを無視する必要があります。オブジェクトは、Disposeメソッドが複数回呼び出された場合に例外をスローしてはなりません。

このルールは、私が上に示されているよう開発者は、using使い捨てのカスケードでsanely文を採用することができる(または多分これは単なるニースの副作用である)ので、存在していることを主張することができます。同様に、CA2202は有用な目的を果たさないため、プロジェクトごとに抑制する必要があります。本当の犯人はDisposeの不完全な実装であり、 CA1065 はそれを処理する必要があります(あなたの責任の下にある場合)。

135
Jordão

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

これらは実際のバグではなく、これらの.NETクラスは複数のDispose()呼び出しに対して回復力があります。ただし、警告を削除する場合は、これらのオブジェクトのusingステートメントを削除する必要があります。そして、コードが例外をスローした場合に何が起こるかを推論するとき、少し苦労します。または、属性を使用して警告を閉じます。それともばかげているので、警告を無視してください。

41
Hans Passant

StreamWriter が破棄されると、ラップされた Stream (ここでは CryptoStream )が自動的に破棄されます。 CryptoStream は、ラップされたものを自動的に破棄します Stream (ここでは MemoryStream )。

したがって、 MemoryStreamCryptoStreamusingステートメントの両方によって破棄されます。そして CryptoStreamStreamWriter および外側の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
dtb

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

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

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

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

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

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

9
Joe

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

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

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

 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は11行目で破棄されるため(usingで使用されているため)、最後のcryptoStreamステートメント内にstreamWriterを返す必要があることに注意してください。 usingステートメント)、これはmemoryStreamを11行目でも破棄するように導きます(memoryStreamを使用してcryptoStreamを作成するため)。

少なくともこのコードは私のために働いた。

編集:

面白いかもしれませんが、GetMemoryStreamメソッドを次のコードに置き換えると、

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

同じ結果が得られます。

2
Jimi

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

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

1
Shiraz Bhaiji

私はこれを正しい方法で解決したかったのです。つまり、警告を抑制せず、すべての使い捨てオブジェクトを正しく廃棄しました。

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();
                }
            }
        }
   }
1
divyanshm

すべての使用を避け、ネストされた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();
        }
    }
0
Harry Saltzman

オブジェクトの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への複数の呼び出しの間違いに対して(できれば)弾力性がありますが、allクラスではなく、プログラマの誤用に対する防御です。

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

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

  • オブジェクトに対してDisposeを1回だけ呼び出します。 usingを使用しないでください
  • disposeを2回呼び出し続け、コードがクラッシュしないことを願っています
  • 警告を抑制する
0
Ian Boyd

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

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();
}

また、ここでvarsを使用して、本当に長いクラス名の繰り返しを避けることを推奨します。

追伸指摘してくれた@Shellshockのおかげで、using in memoryStreamステートメントが範囲外になるため、最初のreturnの括弧を省略できません。

0
Dan Abramov