CA2202, bu durum nasıl çözülür


103

Herhangi biri bana tüm CA2202 uyarılarını aşağıdaki koddan nasıl kaldıracağımı söyleyebilir mi?

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

Uyarı 7 CA2202: Microsoft.Usage: 'cryptoStream' nesnesi, 'CryptoServices.Encrypt (dize, bayt [], bayt [])' yönteminde birden çok kez atılabilir. Bir System.ObjectDisposedException oluşturmaktan kaçınmak için, bir nesnede Dispose'u birden fazla kez çağırmamalısınız .: Satırlar: 34

Uyarı 8 CA2202: Microsoft.Usage: 'memoryStream' nesnesi, 'CryptoServices.Encrypt (string, bayt [], bayt [])' yönteminde birden fazla atılabilir. Bir System.ObjectDisposedException oluşturmaktan kaçınmak için, bir nesnede Dispose'u birden fazla kez çağırmamalısınız .: Satırlar: 34, 37

Bu uyarıları görmek için Visual Studio Code Analysis gerekir (bunlar c # derleyici uyarıları değildir).


1
Bu kod, bu uyarıları oluşturmaz.
Julien Hoarau

1
Bunun için 0 uyarı alıyorum (Uyarı seviyesi 4, VS2010). Ve bu alanda sorun yaşayan biri için lütfen uyarıların metnini de ekleyin.
Henk Holterman

29
CAxxxx uyarıları, Kod Analizi ve FxCop tarafından oluşturulur .
dtb

Bu uyarı gösterilen kod için geçerli değildir - tam olarak bu senaryo için uyarılar bastırılabilir. Kodunuzu gözden geçirip bu değerlendirmeyi kabul ettikten sonra, bunu yönteminizin üzerine yerleştirin: " [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification="BrainSlugs83 said so.")]" - kullanım using System.Diagnostics.CodeAnalysis;blokunuzda bir " " ifadesi bulunduğundan emin olun .
BrainSlugs83

Yanıtlar:


-4

Bu uyarı olmadan derlenir:

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

Düzenleme yorumlara yanıt olarak: Sadece orijinal bir yok olurken bu kod, uyarı yol açmadığından tekrar doğrulanması. Orijinal kodda CryptoStream.Dispose()ve MemoryStream().Dispose() aslında iki kez çağrılır (bu bir sorun olabilir veya olmayabilir).

Değiştirilen kod şu şekilde çalışır: nullatma sorumluluğu başka bir nesneye aktarılır aktarılmaz referanslar olarak ayarlanır . Örneğin memoryStream, kurucuya nullçağrı CryptoStreambaşarılı olduktan sonra olarak ayarlanır . yapıcıya çağrı başarılı olduktan sonra cryptoStreamolarak ayarlanır . Herhangi bir istisna olmazsa , bloğa atılır ve karşılığında ve imha edilir .nullStreamWriterstreamWriterfinallyCryptoStreamMemoryStream


85
-1 Sadece bastırılması gereken bir uyarıya uymak için çirkin kod oluşturmak gerçekten kötü .
Jordão

4
Gelecekte bir noktada düzeltilebilecek bir şey için kodlamanızı kesmemeniz gerektiğini kabul ediyorum, sadece bastırın.
peteski

3
Bu sorunu nasıl çözer? CA2202, memoryStream hala son bloğunda iki kez atılabildiği için hala rapor edilmektedir.
Chris Gessler

3
CryptoStream, dahili olarak MemoryStream üzerinde Dispose çağırdığından, iki kez çağrılabilir, bu da uyarının nedenidir. Çözümünüzü denedim ve hala uyarı alıyorum.
Chris Gessler

2
Oh tanrım, haklısın - senin mantık-mantığınla bir temizleme mantığının karışmasını beklemiyordum ... - bu sadece tuhaf ve şifreli - kesinlikle zekice - ama yine, korkutucu - lütfen bunu üretim kodunda yapmayın; Açık olmak gerekirse, bunun çözmekte olduğu gerçek bir işlevsel sorun olmadığını anlıyorsunuz, değil mi? (Bu nesneleri birden çok kez elden çıkarmak sorun değil.) - Yapabilseydim, olumsuz oyu kaldırırdım (SO engelliyor, cevabı düzenlemeniz gerektiğini söylüyor) - ama bunu sadece gönülsüzce yapardım ... - ve cidden, bunu asla yapma.
BrainSlugs83

142

Bu durumda uyarıları bastırmalısınız. Tek kullanımlık malzemelerle ilgilenen kod tutarlı olmalı ve diğer sınıfların oluşturduğunuz tek kullanımlık malzemelerin sahipliğini almasına ve ayrıca Disposeonları çağırmasına dikkat etmeniz gerekmez.

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

GÜNCELLEME: In IDisposable.Dispose belgelerinde bunu okuyabilir:

Bir nesnenin Dispose yöntemi birden fazla çağrılırsa, nesnenin ilkinden sonraki tüm çağrıları yoksayması gerekir. Dispose yöntemi birden çok kez çağrılırsa nesne bir istisna atmamalıdır.

Geliştiricilerin, usingyukarıda gösterdiğim gibi (ya da belki de bu sadece güzel bir yan etkidir) ifadeyi bir tek kullanımlık malzeme dizisinde mantıklı bir şekilde kullanabilmeleri için bu kuralın var olduğu iddia edilebilir . Aynı şekilde, CA2202 hiçbir yararlı amaca hizmet etmez ve proje açısından bastırılmalıdır. Gerçek suçlu, hatalı bir uygulama olacaktır Disposeve CA1065 bununla ilgilenmelidir (eğer sizin sorumluluğunuzdaysa).


15
Bence bu fxcop'ta bir hata, bu kural tamamen yanlış. Dispose yöntemi asla bir ObjectDisposedException oluşturmamalıdır ve eğer öyleyse o zaman dispose bu şekilde uygulayan kodun yazarına bir hata göndererek onunla ilgilenmelisiniz.
justin.m.chase

15
Diğer başlıkta @HansPassant'a katılıyorum: araç işini yapıyor ve sınıfların beklenmedik bir uygulama ayrıntısı konusunda sizi uyarıyor. Kişisel olarak, gerçek sorunun API'lerin kendilerinin tasarımında olduğunu düşünüyorum. İç içe geçmiş sınıfların varsayılan olarak, başka bir yerde oluşturulan başka bir nesnenin sahipliğini almanın uygun olduğunu varsaymak, oldukça tartışmalı görünüyor. Ortaya çıkan nesne döndürülecekse bunun nerede yararlı olabileceğini görebiliyorum, ancak bu varsayımı varsayılan olarak değiştirmek, normal IDisposable kalıplarını ihlal etmenin yanı sıra sezgisel görünmüyor.
BTJ

8
Ancak msdn, bu tür mesajların bastırılmasını önermez. Bir göz atın: msdn.microsoft.com/en-us/library/…
Adil Mammadov

2
@AdilMammadov bağlantısı için teşekkürler, faydalı bilgiler, ancak microsoft bu konularda her zaman haklı değildir.
Tim Abell

40

Doğru, bu akışlardaki Dispose () yöntemi birden fazla çağrılacak. StreamReader sınıfı, cryptoStream'in 'sahipliğini' alacaktır, böylece streamWriter'ın elden çıkarılması da cryptoStream'i elden çıkaracaktır. Benzer şekilde, CryptoStream sınıfı, memoryStream'in sorumluluğunu üstlenir.

Bunlar tam olarak gerçek hatalar değildir, bu .NET sınıfları birden çok Dispose () çağrısına karşı dayanıklıdır. Ancak uyarıdan kurtulmak istiyorsanız, bu nesneler için using ifadesini bırakmalısınız. Ve kod bir istisna atarsa ​​ne olacağını düşünürken kendinize biraz acı verin. Veya uyarıyı bir öznitelikle kapatın. Ya da aptalca olduğu için uyarıyı görmezden gelin.


10
Sınıfların iç davranışları hakkında özel bilgiye sahip olmak (tek kullanımlık bir diğerinin sahipliğini almak gibi), kişinin yeniden kullanılabilir bir API tasarlamak isteyip istemediğini sormak için çok fazla. Bu yüzden usingifadelerin kalması gerektiğini düşünüyorum . Bu uyarılar gerçekten aptalca.
Jordão

4
@ Jordão - bu araç ne için? Sizi hiç bilmediğiniz iç davranışlar hakkında uyarmak için?
Hans Passant

8
Katılıyorum. Ama yine de usingifadeleri düşürmedim. Yarattığım bir nesneyi elden çıkarmak için başka bir nesneye güvenmek yanlış geliyor. Bu kod için sorun değil, ancak orada Streamve TextWriterdışarıda birçok uygulama var (sadece BCL'de değil). Hepsini kullanacak kod tutarlı olmalıdır.
Jordão

3
Evet, Jordão'ya katılıyorum. Programcının api'nin dahili davranışından haberdar olmasını gerçekten istiyorsanız, işlevinizi DoSomethingAndDisposeStream (Akış akışı, OtherData verileri) olarak adlandırarak konuşun.
ZZZ

5
@HansPassant XmlDocument.Save()Yöntemin Disposesağlanan parametreyi nerede çağıracağının belgelendiğini gösterebilir misiniz ? Bunu belgelerinde Save(XmlWriter)(FxCop hatasını yaşadığım yerde) veya Save()yöntemin kendisinde veya kendi belgelerinde görmüyorum XmlDocument.
Ian Boyd

9

Bir StreamWriter atıldığında, sarılmış Akışı otomatik olarak atar (burada: CryptoStream ). CryptoStream ayrıca sarılmış Akışı otomatik olarak ortadan kaldırır (burada: MemoryStream ).

Dolayısıyla, MemoryStream'iniz hem CryptoStream hem de using ifadesi tarafından düzenlenir . Ve CryptoStream tarafından elden StreamWriter ve dış kullanarak deyimi.


Bazı deneylerden sonra, uyarılardan tamamen kurtulmak imkansız gibi görünüyor. Teorik olarak, MemoryStream'in atılması gerekir, ancak o zaman teorik olarak ToArray yöntemine artık erişemezsiniz. Pratik olarak, bir MemoryStream'in atılmasına gerek yoktur, bu yüzden bu çözüme gidip CA2000 uyarısını bastırırdım.

var memoryStream = new MemoryStream();

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

return memoryStream.ToArray();

"o zaman teorik olarak ToArray yöntemine artık erişemezsiniz" Bu yüzden returnsatırı usingkapsamın kapanış ayracından önce taşıyın .
Ben Voigt

9

Bunu kullanarak yapardım #pragma warning disable.

.NET Framework Yönergeleri, IDisposable.Dispose'un birden çok kez çağrılabilecek şekilde uygulanmasını önerir. Gönderen IDisposable.Dispose MSDN açıklaması :

Dispose yöntemi birden çok kez çağrılırsa nesne bir istisna atmamalıdır

Bu nedenle uyarı neredeyse anlamsız görünüyor:

Bir System.ObjectDisposedException oluşturmaktan kaçınmak için, bir nesnede Dispose'u birden fazla kez çağırmamalısınız.

Standart uygulama yönergelerine uymayan kötü uygulanmış bir IDisposable nesne kullanıyorsanız uyarının yardımcı olabileceği iddia edilebilir. Ancak sizin yaptığınız gibi .NET Framework'teki sınıfları kullanırken, bir #pragma kullanarak uyarıyı bastırmanın güvenli olduğunu söyleyebilirim. Ve IMHO, bu uyarı için MSDN belgelerinde önerildiği gibi çemberlerden geçmek tercih edilir .


4
CA2202 bir Kod Analizi uyarısıdır ve bir derleyici uyarısı değildir. #pragma warning disableyalnızca derleyici uyarılarını bastırmak için kullanılabilir. Bir Kod Analizi uyarısını bastırmak için bir öznitelik kullanmanız gerekir.
Martin Liversage

2

Kodumda benzer sorunlarla karşılaştım.

Yapıcıda MemoryStream(CA2000) istisna oluşursa elden çıkarılabileceği için CA2202 olayının tamamı tetiklenmiş gibi görünüyor .

Bu şu şekilde çözülebilir:

 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 }

memoryStreamSon usingcümlenin (10. satır) içini döndürmemiz gerektiğine dikkat edin çünkü cryptoStream11. satıra yerleştirilir (çünkü streamWriter usingifadede kullanılır ), bu memoryStreamda 11. satıra atılır (çünkü memoryStreamonu oluşturmak için kullanılır cryptoStream).

En azından bu kod benim için çalıştı.

DÜZENLE:

Kulağa komik gelse de, GetMemoryStreamyöntemi aşağıdaki kodla değiştirirseniz,

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

aynı sonucu alırsınız.


1

Kripto akışı, hafıza akışına dayanmaktadır.

Görünüşe göre, kripto-akış atıldığında (kullanımın sonunda) hafıza akışı da atılır, ardından hafıza akışı tekrar atılır.


1

Bunu doğru şekilde çözmek istedim - yani uyarıları bastırmadan ve tüm tek kullanımlık nesneleri doğru bir şekilde atmadan.

3 akımdan 2'sini alan olarak Dispose()çıkardım ve sınıfımın yöntemine yerleştirdim . Evet, IDisposablearayüzü uygulamak aradığınız şey olmayabilir, ancak çözüm dispose(), koddaki tüm rastgele yerlerden gelen çağrılara kıyasla oldukça temiz görünüyor .

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

Konu dışı, ancak URL'leri gruplandırmak için farklı bir biçimlendirme tekniği kullanmanızı öneririm 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();
}

Ayrıca var, gerçekten uzun sınıf isimlerinin tekrarlanmasını önlemek için burada s kullanılmasını savunuyorum .

Ben ilk için omit parantez olamaz işaret için @ShellShock PS sayesinde usingo yapar gibi memoryStreamde returnkapsam dışında açıklamada.


5
MemoryStream.ToArray () kapsam dışında kalmayacak mı?
Polyfun

Bu kesinlikle orijinal kod parçasına eşdeğerdir. Ben sadece senin ifs ile yapabildiğin gibi kıvırcık telleri çıkardım (gerçi bu tekniği usings dışında bir şey için tavsiye etmem ).
Dan Abramov

2
Orijinal kodda, memoryStream.ToArray () ilk kullanımın kapsamı içindeydi; kapsamın dışında tutuyorsunuz.
Polyfun

Çok teşekkür ederim, ifade etmek istediğini şimdi anladım return. Çok doğru. Cevabı bunu yansıtacak şekilde düzenledim.
Dan Abramov

Ben şahsen usingparantezsizin kodu daha kırılgan hale getirdiğini düşünüyorum (yıllarca süren farklılıkları ve birleşmeleri düşünün). joelonsoftware.com/2005/05/11/making-wrong-code-look-wrong & imperialviolet.org/2014/02/22/applebug.html
Tim Abell

0

Tüm kullanımlardan kaçının ve iç içe geçmiş Dispose-Calls kullanın!

    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
Lütfen usingbu durumda neden kaçınmanız gerektiğini açıklayın .
StuperUser

1
Kullanarak ifadesini ortada tutabilirsiniz, ancak diğerlerini çözmeniz gerekir. Mantıksal tutarlı ve her yönden yükseltilebilir bir çözüm elde etmek için tüm kullanımları kaldırmaya karar verdim!
Harry Saltzman

-1

Akışları kullanmadan bayt [] alan ve bayt [] döndüren bu tür bir kod kullandım

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

Bu şekilde tek yapmanız gereken, kodlamaları kullanarak dizeden bayta [] dönüştürmektir.


-1

DisposeNesnelerde birden fazla çağrı görebilmek için kodu açmak istedim . Gerçeklik Bunun ne vardır çağıran Disposenesneler üzerinde iki kez:

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

Çoğu .NET sınıfı (umarız) birden çok çağrı yapma hatasına karşı dirençli olsa da .Dispose, tüm sınıflar programcıların kötüye kullanımına karşı savunmacı değildir .

FX Cop bunu bilir ve sizi uyarır.

Birkaç seçeneğiniz var;

  • Disposeherhangi bir nesneyi yalnızca bir kez çağırın ; kullanmausing
  • dispose'u iki kez aramaya devam edin ve kodun çökmemesini umuyoruz
  • uyarıyı bastır
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.