CA2202, bagaimana mengatasi kasus ini

102

Adakah yang bisa memberi tahu saya cara menghapus semua peringatan CA2202 dari kode berikut?

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

Peringatan 7 CA2202: Microsoft.Usage: Objek 'cryptoStream' dapat dibuang lebih dari sekali dalam metode 'CryptoServices.Encrypt (string, byte [], byte [])'. Untuk menghindari pembuatan System.ObjectDisposedException, Anda tidak boleh memanggil Buang lebih dari satu kali pada suatu objek .: Garis: 34

Peringatan 8 CA2202: Microsoft.Usage: Object 'memoryStream' dapat dibuang lebih dari sekali dalam metode 'CryptoServices.Encrypt (string, byte [], byte [])'. Untuk menghindari pembuatan System.ObjectDisposedException, Anda tidak boleh memanggil Buang lebih dari satu kali pada suatu objek .: Baris: 34, 37

Anda memerlukan Visual Studio Code Analysis untuk melihat peringatan ini (ini bukan peringatan compiler c #).

testalino
sumber
1
Kode ini tidak menghasilkan peringatan ini.
Julien Hoarau
1
Saya mendapatkan 0 peringatan untuk ini (Warn level 4, VS2010). Dan untuk masalah googling seseorang di area ini, mohon tambahkan teks peringatan juga.
Henk Holterman
29
Peringatan CAxxxx dibuat oleh Analisis Kode dan FxCop.
dtb
Peringatan ini tidak berlaku untuk kode yang ditampilkan - peringatan dapat disembunyikan untuk skenario ini. Setelah Anda meninjau kode Anda dan menyetujui penilaian tersebut, tempatkan ini di atas metode Anda: " [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification="BrainSlugs83 said so.")]" - pastikan Anda memiliki using System.Diagnostics.CodeAnalysis;pernyataan " " di blok penggunaan Anda.
BrainSlugs83
2
Silakan
Adil Mammadov

Jawaban:

-3

Ini mengkompilasi tanpa peringatan:

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

Edit sebagai tanggapan atas komentar: Saya baru saja memverifikasi lagi bahwa kode ini tidak menghasilkan peringatan, sedangkan yang asli tidak. Dalam kode asli, CryptoStream.Dispose()dan MemoryStream().Dispose() sebenarnya dipanggil dua kali (yang mungkin menjadi masalah atau tidak).

Kode yang dimodifikasi bekerja sebagai berikut: referensi ditetapkan null, segera setelah tanggung jawab untuk membuang dipindahkan ke objek lain. Misalnya, memoryStreamdisetel ke nullsetelah panggilan ke CryptoStreamkonstruktor berhasil. cryptoStreamdisetel ke null, setelah panggilan ke StreamWriterkonstruktor berhasil. Jika tidak ada pengecualian yang terjadi, streamWriterdibuang di finallyblok dan pada gilirannya akan membuang CryptoStreamdan MemoryStream.

Henrik
sumber
85
-1 Sangat buruk membuat kode jelek hanya untuk mematuhi peringatan yang harus disembunyikan .
Jordão
4
Saya setuju bahwa Anda tidak boleh membantai Anda dengan kode untuk sesuatu yang dapat diperbaiki di beberapa titik di masa depan, cukup tekan.
peteski
3
Bagaimana cara ini mengatasi masalah? CA2202 masih dilaporkan karena memoryStream masih dapat dibuang dua kali di blok terakhir.
Chris Gessler
3
Karena CryptoStream memanggil Buang di MemoryStream secara internal, itu bisa dipanggil dua kali, yang merupakan alasan untuk peringatan tersebut. Saya mencoba solusi Anda dan masih mendapatkan peringatan.
Chris Gessler
2
Ya ampun, Anda benar - saya tidak menyangka akan ada pembersihan-logika yang bercampur dengan ... logika-logika Anda ... - itu hanya aneh dan samar - itu pasti pintar - tapi sekali lagi, menakutkan - tolong jangan lakukan ini dalam kode produksi; untuk menjadi jelas: Anda mengerti bahwa tidak ada masalah fungsional aktual yang dapat diselesaikan, bukan? (Tidak apa-apa untuk membuang objek ini beberapa kali.) - Saya akan menghapus suara negatif jika saya bisa (SO mencegah saya, dikatakan Anda harus mengedit jawabannya) - tetapi saya hanya akan melakukannya dengan enggan ... - dan serius, jangan pernah melakukan ini.
BrainSlugs83
142

Anda harus menyembunyikan peringatan dalam kasus ini. Kode yang berhubungan dengan sekali pakai harus konsisten, dan Anda tidak perlu peduli bahwa kelas lain mengambil kepemilikan atas barang sekali pakai yang Anda buat dan juga memanggilnya 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: Dalam dokumentasi IDisposable.Dispose Anda dapat membaca ini:

Jika metode Dispose suatu objek dipanggil lebih dari sekali, objek tersebut harus mengabaikan semua panggilan setelah yang pertama. Objek tidak boleh mengeluarkan pengecualian jika metode Dispose-nya dipanggil beberapa kali.

Dapat dikatakan bahwa aturan ini ada sehingga pengembang dapat menggunakan usingpernyataan secara masuk akal dalam rangkaian sekali pakai, seperti yang saya tunjukkan di atas (atau mungkin ini hanya efek samping yang bagus). Dengan cara yang sama, maka CA2202 tidak memiliki tujuan yang berguna, dan harus disembunyikan dari segi proyek. Pelaku sebenarnya adalah implementasi yang salah Dispose, dan CA1065 harus membereskannya (jika itu di bawah tanggung jawab Anda).

Jordão
sumber
14
Menurut pendapat saya ini adalah bug di fxcop, aturan ini salah. Metode pembuangan tidak boleh memunculkan ObjectDisposedException dan jika demikian maka Anda harus menanganinya pada saat itu dengan melaporkan bug pada pembuat kode yang mengimplementasikan buang dengan cara ini.
justin.m. mengejar
14
Saya setuju dengan @HansPassant di utas lainnya: alat ini melakukan tugasnya dan memperingatkan Anda tentang detail implementasi kelas yang tidak terduga. Secara pribadi, menurut saya masalah sebenarnya adalah desain API itu sendiri. Memiliki kelas bersarang secara default menganggap tidak masalah untuk mengambil kepemilikan objek lain yang dibuat di tempat lain tampaknya sangat dipertanyakan. Saya bisa melihat di mana yang bisa berguna jika objek yang dihasilkan akan dikembalikan, tetapi default ke asumsi itu tampaknya kontra-intuitif, serta melanggar pola IDisposable normal.
BTJ
8
Tetapi msdn tidak merekomendasikan untuk Menahan jenis pesan ini. Lihat di: msdn.microsoft.com/en-us/library/…
Adil Mammadov
2
Terima kasih untuk tautan @AdilMammadov, info berguna tetapi microsoft tidak selalu benar tentang hal-hal ini.
Tim Abell
40

Memang akurat, metode Dispose () pada aliran ini akan dipanggil lebih dari sekali. Kelas StreamReader akan mengambil 'kepemilikan' dari cryptoStream sehingga membuang streamWriter juga akan membuang cryptoStream. Demikian pula, kelas CryptoStream mengambil alih tanggung jawab untuk memoryStream.

Ini bukan bug yang sebenarnya, kelas .NET ini tahan terhadap beberapa panggilan Dispose (). Tetapi jika Anda ingin menghilangkan peringatan tersebut maka Anda harus menghilangkan pernyataan using untuk objek-objek ini. Dan sakiti diri Anda sedikit ketika memikirkan apa yang akan terjadi jika kode tersebut membuat pengecualian. Atau tutup peringatan dengan atribut. Atau abaikan saja peringatan itu karena itu konyol.

Hans Passant
sumber
10
Harus memiliki pengetahuan khusus tentang perilaku internal kelas (seperti mengambil kepemilikan sekali pakai dari kelas lain) terlalu banyak untuk ditanyakan apakah seseorang ingin merancang API yang dapat digunakan kembali. Jadi saya pikir usingpernyataan itu harus tetap ada. Peringatan ini sangat konyol.
Jordão
4
@ Jordão - bukankah itu gunanya alat? Untuk memperingatkan Anda tentang perilaku internal yang mungkin belum Anda ketahui?
Hans Passant
8
Saya setuju. Tapi, saya tetap tidak akan membatalkan usingpernyataan itu. Rasanya salah jika mengandalkan objek lain untuk membuang objek yang saya buat. Untuk kode ini, tidak apa-apa, tetapi ada banyak implementasi di dalam Streamdan di TextWriterluar sana (tidak hanya di BCL). Kode untuk menggunakan semuanya harus konsisten.
Jordão
3
Ya, setuju dengan Jordão. Jika Anda benar-benar ingin programmer mengetahui perilaku internal api, bicaralah dengan menamai fungsi Anda sebagai DoSomethingAndDisposeStream (Stream stream, data OtherData).
ZZZ
4
@HansPassant Dapatkah Anda menunjukkan di mana itu didokumentasikan bahwa XmlDocument.Save()metode akan memanggil Disposeparameter yang disediakan? saya tidak melihatnya di dokumentasi Save(XmlWriter)(di mana saya mengalami bug FxCop), atau dalam Save()metode itu sendiri, atau dalam dokumentasi XmlDocumentitu sendiri.
Ian Boyd
9

Saat StreamWriter dibuang, StreamWriter akan secara otomatis membuang Stream yang dibungkus (di sini: CryptoStream ). CryptoStream juga secara otomatis membuang Stream yang dibungkus (di sini: MemoryStream ).

Jadi MemoryStream Anda dibuang oleh CryptoStream dan pernyataan menggunakan . Dan CryptoStream Anda dibuang oleh StreamWriter dan pernyataan penggunaan luar .


Setelah beberapa percobaan, tampaknya tidak mungkin untuk menghilangkan peringatan sepenuhnya. Secara teoritis, MemoryStream perlu dibuang, tetapi Anda secara teoritis tidak dapat mengakses metode ToArray lagi. Praktisnya, MemoryStream tidak perlu dibuang, jadi saya akan menggunakan solusi ini dan menekan peringatan CA2000.

var memoryStream = new MemoryStream();

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

return memoryStream.ToArray();
dtb
sumber
9

Saya akan melakukan ini dengan menggunakan #pragma warning disable.

Panduan .NET Framework merekomendasikan untuk mengimplementasikan IDisposable. Buang sedemikian rupa sehingga dapat dipanggil beberapa kali. Dari deskripsi MSDN tentang IDisposable. Buang :

Objek tidak boleh mengeluarkan pengecualian jika metode Dispose-nya dipanggil beberapa kali

Oleh karena itu peringatan itu tampaknya hampir tidak ada artinya:

Untuk menghindari pembuatan System.ObjectDisposedException, Anda tidak boleh memanggil Buang lebih dari satu kali pada suatu objek

Saya kira itu bisa dikatakan bahwa peringatan mungkin berguna jika Anda menggunakan objek IDisposable yang diterapkan dengan buruk yang tidak mengikuti pedoman implementasi standar. Tetapi ketika menggunakan kelas dari .NET Framework seperti yang Anda lakukan, menurut saya aman untuk menekan peringatan menggunakan #pragma. Dan IMHO ini lebih disukai daripada melalui rintangan seperti yang disarankan dalam dokumentasi MSDN untuk peringatan ini .

Joe
sumber
4
CA2202 adalah peringatan Analisis Kode dan bukan peringatan kompilator. #pragma warning disablehanya dapat digunakan untuk menyembunyikan peringatan kompilator. Untuk menekan peringatan Analisis Kode Anda perlu menggunakan atribut.
Martin Liversage
2

Saya dihadapkan dengan masalah serupa dalam kode saya.

Sepertinya seluruh hal CA2202 dipicu karena MemoryStreamdapat dibuang jika pengecualian terjadi pada konstruktor (CA2000).

Ini bisa diselesaikan seperti ini:

 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 }

Perhatikan bahwa kita harus mengembalikan bagian memoryStreamdalam usingpernyataan terakhir (baris 10) karena cryptoStreamdibuang pada baris 11 (karena digunakan dalam streamWriter usingpernyataan), yang mengarah memoryStreamke juga dibuang pada baris 11 (karena memoryStreamdigunakan untuk membuat cryptoStream).

Setidaknya kode ini berhasil untuk saya.

EDIT:

Meskipun terdengar lucu, saya menemukan bahwa jika Anda mengganti GetMemoryStreammetode dengan kode berikut,

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

Anda mendapatkan hasil yang sama.

Jimi
sumber
1

Cryptostream didasarkan pada aliran memori.

Apa yang tampaknya terjadi adalah ketika crypostream dibuang (pada akhir penggunaan) aliran memori juga dibuang, kemudian aliran memori dibuang lagi.

Shiraz Bhaiji
sumber
1

Saya ingin menyelesaikan ini dengan cara yang benar - yaitu tanpa menekan peringatan dan membuang semua objek sekali pakai dengan benar.

Saya menarik 2 dari 3 aliran sebagai ladang dan membuangnya ke dalam Dispose()metode kelas saya. Ya, mengimplementasikan IDisposableantarmuka mungkin tidak selalu menjadi apa yang Anda cari, tetapi solusinya terlihat cukup bersih dibandingkan dengan dispose()panggilan dari semua tempat acak dalam kode.

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

Di luar topik tetapi saya menyarankan Anda untuk menggunakan teknik pemformatan yang berbeda untuk pengelompokan 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();
}

Saya juga menganjurkan penggunaan vars di sini untuk menghindari pengulangan nama kelas yang sangat panjang.

PS Terima kasih kepada @ShellShock karena telah menunjukkan bahwa saya tidak dapat menghilangkan kawat gigi untuk pertama kali usingkarena akan membuat pernyataan memoryStreamdi returnluar ruang lingkup.

Dan Abramov
sumber
5
Tidakkah memoryStream.ToArray () berada di luar cakupan?
Polyfun
Ini benar-benar sama dengan potongan kode asli. Saya baru saja menghilangkan kawat gigi keriting, seperti Anda dapat melakukannya dengan ifs (meskipun saya tidak akan menyarankan teknik ini untuk hal lain selain usings).
Dan Abramov
2
Dalam kode asli, memoryStream.ToArray () berada di dalam ruang lingkup penggunaan pertama; Anda mendapatkannya di luar ruang lingkup.
Polyfun
Terima kasih banyak, saya baru menyadari returnpernyataan yang Anda maksudkan . Benar sekali. Saya mengedit jawaban untuk mencerminkan ini.
Dan Abramov
Saya pribadi berpikir usingtanpa kurung kurawal membuat kode lebih rapuh (pikirkan tahun-tahun diff dan penggabungan). joelonsoftware.com/2005/05/11/making-wrong-code-look-wrong & imperialviolet.org/2014/02/22/applebug.html
Tim Abell
0

Hindari semua penggunaan dan gunakan Dispose-Calls bersarang!

    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();
        }
    }
Harry Saltzman
sumber
1
Tolong jelaskan mengapa Anda harus menghindari usingdalam kasus ini.
StuperUser
1
Anda bisa menyimpan pernyataan menggunakan di tengah, tetapi Anda harus menyelesaikan yang lain. Untuk mendapatkan solusi logis yang koheren dan ke segala arah yang dapat diupgrade, saya memutuskan untuk menghapus semua penggunaan!
Harry Saltzman
0

Saya hanya ingin membuka kodenya sehingga kita bisa melihat beberapa panggilan ke Disposeobjek:

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

Meskipun sebagian besar kelas .NET (mudah-mudahan) tahan terhadap kesalahan beberapa panggilan ke .Dispose, tidak semua kelas bersifat defensif terhadap penyalahgunaan programmer.

FX Cop mengetahui hal ini, dan memperingatkan Anda.

Anda punya beberapa pilihan;

  • hanya menelepon Disposesekali pada objek apa pun; jangan gunakanusing
  • terus menelepon buang dua kali, dan semoga kodenya tidak macet
  • tekan peringatan itu
Ian Boyd
sumber
-1

Saya menggunakan jenis kode ini yang mengambil byte [] dan mengembalikan byte [] tanpa menggunakan aliran

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

Dengan cara ini yang harus Anda lakukan adalah konversi dari string ke byte [] menggunakan pengkodean.

Luka Rahne
sumber