visual studio code c# .net code-analysis fxcop

studio - c# code analyzer



CA2202, cómo resolver este caso (12)

¡Evite todos los usos y use Dispose-Calls anidadas!

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

¿Alguien puede decirme cómo eliminar todas las advertencias de CA2202 del siguiente código?

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

Advertencia 7 CA2202: Microsoft.Usage: El objeto ''cryptoStream'' puede eliminarse más de una vez en el método ''CryptoServices.Encrypt (cadena, byte [], byte [])''. Para evitar generar una excepción System.ObjectDisposedException, no debe llamar a Dispose más de una vez en un objeto .: Lines: 34

Advertencia 8 CA2202: Microsoft.Usage: El objeto ''memoryStream'' puede eliminarse más de una vez en el método ''CryptoServices.Encrypt (cadena, byte [], byte [])''. Para evitar generar una excepción System.ObjectDisposedException, no debe llamar a Dispose más de una vez en un objeto .: Lines: 34, 37

Necesita Visual Studio Code Analysis para ver estas advertencias (estas no son advertencias del compilador de c #).


Bueno, es preciso, el método Dispose () en estas transmisiones se llamará más de una vez. La clase StreamReader asumirá la "propiedad" de cryptoStream, por lo que la eliminación de streamWriter también eliminará cryptoStream. Del mismo modo, la clase CryptoStream asume la responsabilidad del memoryStream.

Estos no son exactamente errores reales, estas clases .NET son resistentes a múltiples llamadas Dispose (). Pero si quiere deshacerse de la advertencia, debe abandonar la declaración de uso de estos objetos. Y sienta un poco de dolor al razonar qué pasará si el código arroja una excepción. O apague la advertencia con un atributo. O simplemente ignore la advertencia ya que es una tontería.


Cryptostream se basa en la corriente de memoria.

Lo que parece estar sucediendo es que cuando la capa criogénica está dispuesta (al final de su uso) la corriente de memoria también está dispuesta, entonces la corriente de memoria está dispuesta de nuevo.


Cuando se StreamWriter un StreamWriter , este eliminará automáticamente el Stream envuelto (aquí: el CryptoStream ). CryptoStream también CryptoStream automáticamente el Stream envuelto (aquí: el MemoryStream ).

Entonces, su MemoryStream está dispuesta tanto por CryptoStream como por la instrucción using . Y su CryptoStream está dispuesto por StreamWriter y la declaración de uso externa.

Después de algunos experimentos, parece ser imposible deshacerse de las advertencias por completo. Teóricamente, el MemoryStream necesita eliminarse, pero en teoría ya no podría acceder a su método ToArray. Prácticamente, no es necesario desechar un MemoryStream, así que iría con esta solución y suprimiría la advertencia CA2000.

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


Debería suprimir las advertencias en este caso. El código que trata con productos desechables debe ser coherente, y no debería preocuparse de que otras clases tomen posesión de los desechables que usted creó y también llame a Dispose sobre ellos.

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

ACTUALIZACIÓN: En la documentación de IDisposable.Dispose , puede leer esto:

Si el método Dispose de un objeto se llama más de una vez, el objeto debe ignorar todas las llamadas después de la primera. El objeto no debe lanzar una excepción si su método Dispose se llama varias veces.

Se puede argumentar que esta regla existe para que los desarrolladores puedan emplear la declaración de using sana en una cascada de elementos desechables, como he demostrado anteriormente (o tal vez esto es solo un buen efecto secundario). Por la misma razón, entonces, CA2202 no tiene ningún propósito útil, y debe suprimirse en cuanto a proyectos. El verdadero culpable sería una implementación defectuosa de Dispose , y CA1065 debería encargarse de eso (si es bajo su responsabilidad).


Esto compila sin previo aviso:

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

Editar en respuesta a los comentarios: Acabo de verificar nuevamente que este código no genera la advertencia, mientras que el original sí lo hace. En el código original, CryptoStream.Dispose() y MemoryStream().Dispose( ) se llaman dos veces (lo que puede o no ser un problema).

El código modificado funciona de la siguiente manera: las referencias se establecen como null , tan pronto como la responsabilidad de la eliminación se transfiera a otro objeto. Eg memoryStream se establece en null después de que la llamada al constructor CryptoStream . cryptoStream se establece en null , después de que la llamada al constructor de StreamWriter tuvo éxito. Si no se produce ninguna excepción, streamWriter se elimina en el bloque finally y, a su vez, CryptoStream y MemoryStream .


Fuera del tema pero le sugiero que use una técnica de formateo diferente para agrupar using s:

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

También defiendo usar var s aquí para evitar repeticiones de nombres de clase realmente largos.

PD: Gracias a @ShellShock por señalar que no puedo omitir las llaves por primera vez ya que harían que la memoryStream de return memoryStream fuera del alcance.


Haría esto usando #pragma warning disable .

Las Directrices de .NET Framework recomiendan implementar IDisposable.Dispose de tal forma que se pueda invocar varias veces. De la descripción de MSDN de IDisposable.Dispose :

El objeto no debe lanzar una excepción si su método Dispose se llama varias veces

Por lo tanto, la advertencia parece ser casi sin sentido:

Para evitar generar una excepción System.ObjectDisposedException, no debe llamar a Dispose más de una vez en un objeto

Supongo que podría argumentarse que la advertencia puede ser útil si está utilizando un objeto IDisposable mal implementado que no sigue las pautas de implementación estándar. Pero al usar clases de .NET Framework como lo hace, diría que es seguro suprimir la advertencia con un #pragma. Y en mi humilde opinión esto es preferible a pasar por aros como se sugiere en la documentación de MSDN para esta advertencia .


Me enfrenté con problemas similares en mi código.

Parece que se activó todo el CA2202 porque MemoryStream se puede eliminar si se produce una excepción en el constructor (CA2000).

Esto podría resolverse así:

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 }

Observe que tenemos que devolver el memoryStream dentro de la última instrucción de using (línea 10) porque cryptoStream se elimina en la línea 11 (porque se usa en la instrucción de using streamWriter ), lo que memoryStream que también se memoryStream en la línea 11 (porque memoryStream se usa para crea el cryptoStream ).

Al menos este código funcionó para mí.

EDITAR:

Por divertido que parezca, descubrí que si reemplazas el método GetMemoryStream con el siguiente código,

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

obtienes el mismo resultado


Quería resolver esto de la manera correcta, es decir, sin suprimir las advertencias y desechar correctamente todos los objetos desechables.

Saqué 2 de las 3 secuencias como campos y las eliminé en el método Dispose() de mi clase. Sí, la implementación de la interfaz IDisposable puede no ser necesariamente lo que está buscando, pero la solución se ve bastante limpia en comparación con las llamadas a dispose() de todos los lugares aleatorios en el código.

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


Solo quería desenvolver el código para que podamos ver varias llamadas a Dispose en los objetos:

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

Si bien la mayoría de las clases .NET son (con suerte) resistentes ante el error de múltiples llamadas a .Dispose , no todas las clases son tan defensivas contra el mal uso del programador.

FX Cop lo sabe y te advierte.

Tienes algunas opciones;

  • solo llame a Dispose una vez sobre cualquier objeto; no uses using
  • sigue llamando descarta dos veces y espera que el código no se cuelgue
  • suprimir la advertencia

Usé este tipo de código que toma byte [] y return byte [] sin usar streams

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

De esta forma, todo lo que tienes que hacer es convertir de cadena a byte [] usando codificaciones.