2011-11-18 10 views
11

ho il seguente codiceCorretta attuazione IDisposable per questo codice

public static byte[] Compress(byte[] CompressMe) 
{ 
    using (MemoryStream ms = new MemoryStream()) 
    { 
     using (GZipStream gz = new GZipStream(ms, CompressionMode.Compress,true)) 
     { 
      gz.Write(CompressMe, 0, CompressMe.Length); 
      ms.Position = 0; 
      byte[] Result = new byte[ms.Length]; 
      ms.Read(Result, 0, (int)ms.Length); 
      return Result; 
     } 
    } 
} 

Questo funziona bene, ma quando ho eseguito l'analisi del codice su di esso, si tratta con il seguente messaggio

CA2202 : Microsoft.Usage : Object 'ms' can be disposed more than once in 
method 'Compression.Compress(byte[])'. To avoid generating a 
System.ObjectDisposedException you should not call Dispose more than one 
time on an object. 

Per quanto per quanto mi riguarda, quando GZipStream è eliminato, lascia aperto il sottostante Stream (ms), a causa dell'ultimo parametro del costruttore (leaveOpen = true).

Se cambio un po 'il mio codice .. rimuovere il 'usando' blocco intorno al MemoryStream e cambiare il parametro 'leaveOpen' false ..

public static byte[] Compress(byte[] CompressMe) 
{ 
    MemoryStream ms = new MemoryStream(); 
    using (GZipStream gz = new GZipStream(ms, CompressionMode.Compress, false)) 
    { 
     gz.Write(CompressMe, 0, CompressMe.Length); 
     ms.Position = 0; 
     byte[] Result = new byte[ms.Length]; 
     ms.Read(Result, 0, (int)ms.Length); 
     return Result; 
    } 
} 

Questo poi esce con ..

CA2000 : Microsoft.Reliability : In method 'Compression.Compress(byte[])', 
object 'ms' is not disposed along all exception paths. Call 
System.IDisposable.Dispose on object 'ms' before all references to 
it are out of scope. 

Non riesco a vincere .. (a meno che non mi sfugga qualcosa di ovvio) Ho provato varie cose, come mettere un tentativo/finalmente intorno al blocco, e Smaltimento di MemoryStream lì, ma o dice che io lo smaltisco due volte, o per niente !!

+5

È strano. Da [msdn docs] (http://msdn.microsoft.com/en-us/library/b1yfkh5e.aspx): [...] Il metodo di smaltimento [...] dovrebbe essere richiamabile più volte senza generare un'eccezione (ObjectDisposedException). – oleksii

+0

CA2000 è una pita enorme. Nella mia esperienza genera più falsi positivi rispetto agli avvertimenti di genuiune. Tutto ciò che [il lupo che piange] (http://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf) ora significa che io tendo a ignorare/sopprimere CA2000 ogni volta che emerge. – LukeH

+1

Non puoi vincere. Correggi il bug nel tuo codice, gz ha bisogno di Flush() o chiuso per produrre tutti i byte. –

risposta

1

che a volte è il problema con la gestione CodeAnalysis, a volte si semplicemente non può vincere e si deve scegliere il male minore ™.

In questa situazione, credo che l'implementazione corretta sia il secondo esempio. Perché?Secondo .NET Reflector, l'attuazione di GZipStream.Dispose() disporrà del l'MemoryStream per voi come GZipStreampossiede il MemoryStream.

parti pertinenti GZipStream classe inferiore:

public GZipStream(Stream stream, CompressionMode mode, bool leaveOpen) 
{ 
    this.deflateStream = new DeflateStream(stream, mode, leaveOpen, true); 
} 

protected override void Dispose(bool disposing) 
{ 
    try 
    { 
     if (disposing && (this.deflateStream != null)) 
     { 
      this.deflateStream.Close(); 
     } 
     this.deflateStream = null; 
    } 
    finally 
    { 
     base.Dispose(disposing); 
    } 
} 

Come non si vuole disabilitare la regola del tutto, è possibile sopprimere per questo metodo solo con utilizzando l'attributo CodeAnalysis.SupressMessage.

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability ", "CA2000:?", Justification = "MemoryStream will be disposed by the GZipStream.")] 

Nota: Avrete inserire il nome completo regola (cioè CA2000:?) come io non sapevo cosa fosse dal messaggio di errore che hai postato.

HTH,

EDIT:

@CodeInChaos:

Guardando più in profondità alla realizzazione DeflateStream.Dispose Credo che ancora disporrà della MemoryStream per voi indipendentemente dall'opzione leaveOpen come si chiama il base.Dispose().

MODIFICA Ignorare quanto sopra a DeflateStream.Dispose. Stavo guardando l'implementazione sbagliata in Reflector. Vedi i commenti per i dettagli.

+0

Sembra che stia inoltrando il lavoro di smaltimento il flusso di memoria a 'DeflateStream', che probabilmente lo farà solo se il parametro' leaveOpen' è false. – CodesInChaos

+0

@CodeInChaos: aggiornata la mia risposta con maggiori dettagli. Credo di aver interpretato il codice correttamente. – Dennis

+0

Imposta il campo '_stream' su' null' prima di chiamare 'base.Dispose'. Ma in quel caso il metodo che hai postato dovrebbe ... – CodesInChaos

3

Da this page in the MSDN

Stream stream = null; 

try 
{ 
    stream = new FileStream("file.txt", FileMode.OpenOrCreate); 
    using (StreamWriter writer = new StreamWriter(stream)) 
    { 
     stream = null; 
     // Use the writer object... 
    } 
} 
finally 
{ 
    if(stream != null) 
     stream.Dispose(); 
} 

E 'il try ... finally che manca dalla vostra soluzione che causa il secondo messaggio.

Se questo:

GZipStream gz = new GZipStream(ms, CompressionMode.Compress, false) 

fallisce il flusso non viene smaltito.

+0

Ho effettivamente provato l'esempio dalla pagina MSDN, ma mi è venuto in mente lo stesso errore di analisi del codice. –

+0

inoltre, se il controlo GZip fallisce, dovrebbe comunque abbandonare i blocchi "using" correttamente .. questa è l'idea, vero? –

+0

Questo è molto strano ... quando analizzo il codice nella mia risposta non produce errori/avvertenze –

0

devi andare vecchia scuola:

public static byte[] Compress(byte[] CompressMe) 
{ 
    MemoryStream ms = null; 
    GZipStream gz = null; 
    try 
    { 
     ms = new MemoryStream(); 
     gz = new GZipStream(ms, CompressionMode.Compress, true); 
     gz.Write(CompressMe, 0, CompressMe.Length); 
     gz.Flush(); 
     return ms.ToArray(); 
    } 
    finally 
    { 
     if (gz != null) 
     { 
      gz.Dispose(); 
     } 
     else if (ms != null) 
     { 
      ms.Dispose(); 
     } 
    } 
} 

sembra orribile lo so, ma è l'unico modo per placare l'avvertimento.

Personalmente, non mi piace scrivere codice come questo, quindi basta sopprimere l'avviso di smaltimento multipla (ove applicabile) sulla base del fatto che le chiamate di Dispose dovrebbero essere idempotenti (ed è in questo caso).

+0

sì, sono tendenzialmente d'accordo, sono sicuro che posso aggirare questo, sembra solo pazzesco che il comando using {} dovrebbe fare attenzione di cose come questa e semplifica il codice. –

+0

Il blocco using è anche necessario dato che l'uso è semplicemente zucchero sintetico per try/finally? Non è possibile che tutto lo smaltimento avvenga nel blocco finale? –

6

Oltre al problema dell'eliminazione, anche il codice è danneggiato. Dovresti chiudere lo zip stream prima di leggere i dati.

Inoltre esiste già un metodo ToArray() su MemoryStream, non è necessario implementarlo da soli.

using (MemoryStream ms = new MemoryStream()) 
{ 
    using (GZipStream gz = new GZipStream(ms, CompressionMode.Compress,true)) 
    { 
     gz.Write(CompressMe, 0, CompressMe.Length); 
    } 
    return ms.ToArray(); 
} 

Vorrei semplicemente sopprimere l'avviso, poiché è un falso positivo. L'analisi del codice è lì per servirti, non il contrario.

+0

+1. Credo inoltre che l'errore sia un * falso positivo * e che, dopo averlo analizzato, selezioni l'implementazione più corretta e sopprima l'avviso/regola. – Dennis

+0

+1 Soprattutto per il fatto che si tratta di strumenti che dovrebbero funzionare per noi, non che noi lavoriamo per loro. –

+0

grazie per aver segnalato il metodo ToArray(). –

2

In realtà chiamare in modo efficace due volte sul flusso di memoria non causerà alcun problema, sarebbe facile codificarlo all'interno della classe MemoryStream e sui test che Microsoft sembra avere. Pertanto, se non si desidera sopprimere la regola un'altra alternativa è dividere il metodo in due:

public static byte[] Compress(byte[] CompressMe) 
    { 
     using (MemoryStream ms = new MemoryStream()) 
     { 
      return Compress(CompressMe, ms); 
     } 
    } 

    public static byte[] Compress(byte[] CompressMe, MemoryStream ms) 
    { 
     using (GZipStream gz = new GZipStream(ms, CompressionMode.Compress, true)) 
     { 
      gz.Write(CompressMe, 0, CompressMe.Length); 
      ms.Position = 0; 
      byte[] Result = new byte[ms.Length]; 
      ms.Read(Result, 0, (int)ms.Length); 
      return Result; 
     } 
    }