CA2202, come risolvere questo caso


102

Qualcuno può dirmi come rimuovere tutti gli avvisi CA2202 dal seguente codice?

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

Avviso 7 CA2202: Microsoft.Usage: L'oggetto 'cryptoStream' può essere eliminato più di una volta nel metodo 'CryptoServices.Encrypt (string, byte [], byte [])'. Per evitare di generare un'eccezione System.ObjectDisposedException, non chiamare Dispose più di una volta su un oggetto .: Righe: 34

Avviso 8 CA2202: Microsoft.Usage: L'oggetto "memoryStream" può essere eliminato più di una volta nel metodo "CryptoServices.Encrypt (string, byte [], byte [])". Per evitare di generare un'eccezione System.ObjectDisposedException, non chiamare Dispose più di una volta su un oggetto .: Righe: 34, 37

È necessario Visual Studio Code Analysis per visualizzare questi avvisi (non sono avvisi del compilatore c #).


1
Questo codice non genera questi avvisi.
Julien Hoarau

1
Ricevo 0 avvisi per questo (livello di avviso 4, VS2010). E per chi cerca su Google problemi in quest'area, si prega di aggiungere anche il testo degli avvisi.
Henk Holterman

29
Gli avvisi CAxxxx vengono generati da Code Analysis e FxCop.
dtb

Questo avviso non si applica al codice mostrato: gli avvisi possono essere soppressi esattamente per questo scenario. Dopo aver esaminato il codice e accettato la valutazione, posiziona questo sopra il tuo metodo: " [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification="BrainSlugs83 said so.")]" - assicurati di avere una using System.Diagnostics.CodeAnalysis;dichiarazione " " nel blocco degli utilizzi.
BrainSlugs83

Risposte:


-3

Questo si compila senza preavviso:

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

Modifica in risposta ai commenti: ho appena verificato di nuovo che questo codice non genera l'avviso, mentre quello originale sì. Nel codice originale CryptoStream.Dispose()eMemoryStream().Dispose( ) vengono effettivamente chiamati due volte (il che può essere un problema o meno).

Il codice modificato funziona come segue: i riferimenti vengono impostati su null, non appena la responsabilità dello smaltimento viene trasferita a un altro oggetto. Ad esempio, memoryStreamè impostato su nulldopo che la chiamata al CryptoStreamcostruttore è riuscita. cryptoStreamè impostato su null, dopo che la chiamata al StreamWritercostruttore è riuscita. Se non si verifica alcuna eccezione, streamWriterviene smaltito nel finallyblocco ea sua volta smaltirà CryptoStreame MemoryStream.


85
-1 È davvero brutto creare codice brutto solo per rispettare un avviso che dovrebbe essere soppresso .
Jordão

4
Sono d'accordo sul fatto che non dovresti macellare il codice per qualcosa che potrebbe finire per essere risolto in futuro, ma semplicemente sopprimere.
peteski

3
Come risolve il problema? CA2202 è ancora segnalato perché memoryStream può ancora essere smaltito due volte nel blocco finalmente.
Chris Gessler

3
Poiché CryptoStream chiama internamente Dispose su MemoryStream, potrebbe essere chiamato due volte, motivo per cui l'avviso. Ho provato la tua soluzione e continuo a ricevere l'avviso.
Chris Gessler

2
Oh accidenti, hai ragione - non mi aspettavo che ci fosse logica di pulizia mescolata con la tua ... logica-logica ... - è solo bizzarro e criptico - è certamente intelligente - ma ancora, spaventoso - per favore non farlo nel codice di produzione; per essere chiari: capisci che non ci sono problemi funzionali reali che questo sta risolvendo, giusto? (Va bene gettare questi oggetti più volte.) - Rimuoverei il voto negativo se potessi (COSÌ mi impedisce, dice che devi modificare la risposta) - ma lo farei solo con riluttanza ... - e seriamente, non farlo mai.
BrainSlugs83

142

In questo caso dovresti sopprimere gli avvisi. Il codice che si occupa degli articoli usa e getta dovrebbe essere coerente e non dovresti preoccuparti che altre classi si assumano la proprietà degli articoli usa e getta che hai creato e Disposeli richiamino .

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

AGGIORNAMENTO: Nella documentazione di IDisposable.Dispose puoi leggere questo:

Se il metodo Dispose di un oggetto viene chiamato più di una volta, l'oggetto deve ignorare tutte le chiamate successive alla prima. L'oggetto non deve generare un'eccezione se il suo metodo Dispose viene chiamato più volte.

Si può sostenere che questa regola esiste in modo che gli sviluppatori possano utilizzare l' usingistruzione in modo sano in una cascata di articoli usa e getta, come ho mostrato sopra (o forse questo è solo un bell'effetto collaterale). Per lo stesso motivo, quindi, CA2202 non ha alcuno scopo utile e dovrebbe essere soppresso dal punto di vista del progetto. Il vero colpevole sarebbe un'implementazione difettosa di Disposee CA1065 dovrebbe occuparsene (se è sotto la tua responsabilità).


14
Secondo me questo è un bug in fxcop, questa regola è semplicemente sbagliata. Il metodo dispose non dovrebbe mai generare un'eccezione ObjectDisposedException e se lo fa allora dovresti affrontarla in quel momento segnalando un bug sull'autore del codice che implementa dispose in questo modo.
justin.m

14
Sono d'accordo con @HansPassant nell'altro thread: lo strumento sta facendo il suo lavoro e ti avverte di un dettaglio inaspettato di implementazione delle classi. Personalmente, penso che il vero problema sia il design delle API stesse. Il fatto che le classi annidate presumano che sia corretto assumere la proprietà di un altro oggetto creato altrove sembra altamente discutibile. Posso vedere dove ciò potrebbe essere utile se l'oggetto risultante dovesse essere restituito, ma l'impostazione predefinita di tale assunto sembra controintuitivo, oltre a violare i normali schemi IDisposable.
BTJ

8
Ma msdn non consiglia di eliminare questo tipo di messaggio. Dai un'occhiata a: msdn.microsoft.com/en-us/library/…
Adil Mammadov

2
Grazie per il link @AdilMammadov, informazioni utili ma microsoft non ha sempre ragione su queste cose.
Tim Abell

40

Bene, è accurato, il metodo Dispose () su questi flussi verrà chiamato più di una volta. La classe StreamReader assumerà la "proprietà" di cryptoStream, quindi l'eliminazione di streamWriter eliminerà anche cryptoStream. Allo stesso modo, la classe CryptoStream si assume la responsabilità per memoryStream.

Questi non sono esattamente bug reali, queste classi .NET sono resistenti a più chiamate Dispose (). Ma se vuoi sbarazzarti dell'avviso, dovresti eliminare l'istruzione using per questi oggetti. E addolorati un po 'quando pensi a cosa succederà se il codice genera un'eccezione. Oppure chiudi l'avvertimento con un attributo. O semplicemente ignora l'avvertimento poiché è sciocco.


10
Dover avere una conoscenza speciale del comportamento interno delle classi (come un usa e getta che si appropria di un'altra) è troppo per chiedere se si vuole progettare un'API riutilizzabile. Quindi penso che le usingdichiarazioni dovrebbero rimanere. Questi avvertimenti sono davvero stupidi.
Jordão

4
@ Jordão - non è a questo che serve lo strumento? Per avvertirti di comportamenti interni di cui potresti non essere a conoscenza?
Hans Passant

8
Sono d'accordo. Ma ancora non lascerei cadere le usingdichiarazioni. È semplicemente sbagliato fare affidamento su un altro oggetto per smaltire un oggetto che ho creato. Per questo codice va bene, ma ci sono molte implementazioni di Streame TextWriterlà fuori (non solo su BCL). Il codice per usarli tutti dovrebbe essere coerente.
Jordão

3
Sì, d'accordo con Jordão. Se vuoi davvero che il programmatore sia a conoscenza del comportamento interno dell'API, allora parla chiamando la tua funzione come DoSomethingAndDisposeStream (Stream stream, OtherData data).
ZZZ

4
@ HansPassant Puoi indicare dove è documentato che il XmlDocument.Save()metodo chiamerà Disposeil parametro fornito? non lo vedo nella documentazione di Save(XmlWriter)(dove sto riscontrando il bug di FxCop), o nel Save()metodo stesso, o nella documentazione XmlDocumentstessa.
Ian Boyd,

9

Quando uno StreamWriter viene eliminato, eliminerà automaticamente lo Stream avvolto (qui: CryptoStream ). CryptoStream dispone anche automaticamente lo Stream avvolto (qui: MemoryStream ).

Quindi il tuo MemoryStream viene eliminato sia dal CryptoStream che dall'istruzione using . E il tuo CryptoStream viene disposto dallo StreamWriter e dall'istruzione using esterna .


Dopo alcuni esperimenti, sembra impossibile eliminare completamente gli avvertimenti. Teoricamente, MemoryStream deve essere eliminato, ma in teoria non potresti più accedere al suo metodo ToArray. In pratica, non è necessario eliminare un MemoryStream, quindi sceglierei questa soluzione e sopprimerei l'avviso 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

Lo farei usando #pragma warning disable .

Le linee guida di .NET Framework consigliano di implementare IDisposable.Dispose in modo tale che possa essere richiamato più volte. Dalla descrizione MSDN di IDisposable.Dispose :

L'oggetto non deve generare un'eccezione se il suo metodo Dispose viene chiamato più volte

Pertanto l'avvertimento sembra essere quasi privo di significato:

Per evitare di generare un'eccezione System.ObjectDisposedException, non chiamare Dispose più di una volta su un oggetto

Immagino che si potrebbe sostenere che l'avviso potrebbe essere utile se si utilizza un oggetto IDisposable implementato male che non segue le linee guida di implementazione standard. Ma quando si utilizzano classi da .NET Framework come si fa, direi che è sicuro sopprimere l'avviso utilizzando un #pragma. E IMHO questo è preferibile ai cerchi come suggerito nella documentazione MSDN per questo avviso .


4
CA2202 è un avviso di analisi del codice e non un avviso del compilatore. #pragma warning disablepuò essere utilizzato solo per sopprimere gli avvisi del compilatore. Per sopprimere un avviso di analisi del codice è necessario utilizzare un attributo.
Martin Liversage

2

Ho dovuto affrontare problemi simili nel mio codice.

Sembra che l'intera operazione CA2202 sia stata attivata perché MemoryStreampuò essere eliminata se si verifica un'eccezione nel costruttore (CA2000).

Questo potrebbe essere risolto in questo modo:

 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 }

Si noti che dobbiamo restituire l' memoryStreaminterno dell'ultima usingistruzione (riga 10) perché cryptoStreamviene eliminata alla riga 11 (perché è usata streamWriter usingnell'istruzione), il che porta memoryStreama essere eliminata anche alla riga 11 (perché memoryStreamviene utilizzata per creare il cryptoStream).

Almeno questo codice ha funzionato per me.

MODIFICARE:

Per quanto possa sembrare divertente, ho scoperto che se sostituisci il GetMemoryStreammetodo con il seguente codice,

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

ottieni lo stesso risultato.


1

Il cryptostream è basato sul flusso di memoria.

Quello che sembra accadere è che quando il crypostream viene smaltito (al termine dell'uso) anche il flusso di memoria viene smaltito, quindi il flusso di memoria viene nuovamente smaltito.


1

Volevo risolvere questo problema nel modo giusto, ovvero senza sopprimere gli avvertimenti e smaltire correttamente tutti gli oggetti usa e getta.

Ho estratto 2 dei 3 flussi come campi e li Dispose()ho disposti nel metodo della mia classe. Sì, l'implementazione IDisposabledell'interfaccia potrebbe non essere necessariamente ciò che stai cercando, ma la soluzione sembra abbastanza pulita rispetto alle dispose()chiamate da tutte le posizioni casuali nel codice.

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

Fuori tema ma ti suggerirei di utilizzare una tecnica di formattazione diversa per raggruppare usingi messaggi:

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

Sostengo anche l'utilizzo di var s qui per evitare ripetizioni di nomi di classi molto lunghi.

PS Grazie a @ShellShock per aver sottolineato che non posso omettere prima le parentesi graffe usingcome farebbe memoryStreamin una returndichiarazione fuori dall'ambito.


5
MemoryStream.ToArray () non sarà fuori ambito?
Polyfun

Questo è assolutamente equivalente al pezzo di codice originale. Ho appena omesso le parentesi graffe, proprio come puoi farlo con ifs (anche se non consiglierei questa tecnica per nient'altro che usings).
Dan Abramov,

2
Nel codice originale, memoryStream.ToArray () era all'interno dell'ambito del primo utilizzo; ce l'hai al di fuori dell'ambito.
Polyfun

Grazie mille, ho appena capito che intendevi una returndichiarazione. Così vero. Ho modificato la risposta per riflettere questo.
Dan Abramov,

Personalmente penso che il usingsenza parentesi graffe renda il codice più fragile (pensa ad anni di differenze e fusioni). joelonsoftware.com/2005/05/11/making-wrong-code-look-wrong & imperialviolet.org/2014/02/22/applebug.html
Tim Abell

0

Evita tutti gli utilizzi e usa Dispose-Calls nidificate!

    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
Spiega perché dovresti evitare usingin questo caso.
StuperUser

1
Potresti mantenere l'istruzione using nel mezzo, ma devi risolvere le altre. Per ottenere una soluzione logica coerente e aggiornabile in tutte le direzioni ho deciso di rimuovere tutti gli utilizzi!
Harry Saltzman

0

Volevo solo scartare il codice in modo da poter vedere più chiamate a Disposesugli oggetti:

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

Sebbene la maggior parte delle classi .NET sia (si spera) resiliente contro l'errore di più chiamate a .Dispose, non tutte le classi sono così difensive contro l'uso improprio del programmatore.

FX Cop lo sa e ti avverte.

Hai poche scelte;

  • chiama solo Disposeuna volta su qualsiasi oggetto; non usareusing
  • continua a chiamare dispose due volte e spero che il codice non si blocchi
  • sopprimere l'avvertimento

-1

Ho usato questo tipo di codice che accetta byte [] e restituisce byte [] senza utilizzare flussi

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

In questo modo tutto ciò che devi fare è convertire da stringa a byte [] usando le codifiche.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.