CA2202, como solucionar este caso

102

¿Alguien puede decirme cómo eliminar todas las advertencias 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' se puede eliminar más de una vez en el método 'CryptoServices.Encrypt (string, byte [], byte [])'. Para evitar generar una System.ObjectDisposedException, no debe llamar a Dispose más de una vez en un objeto .: Líneas: 34

Advertencia 8 CA2202: Microsoft.Usage: El objeto 'memoryStream' se puede eliminar más de una vez en el método 'CryptoServices.Encrypt (string, byte [], byte [])'. Para evitar generar una System.ObjectDisposedException, no debe llamar a Dispose más de una vez en un objeto .: Líneas: 34, 37

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

testalino
fuente
1
Este código no genera estas advertencias.
Julien Hoarau
1
Recibo 0 advertencias para esto (Nivel de advertencia 4, VS2010). Y para alguien que busque en Google problemas en esta área, por favor agregue el texto de las advertencias también.
Henk Holterman
29
Las advertencias CAxxxx son generadas por Code Analysis y FxCop.
dtb
Esta advertencia no se aplica al código mostrado; las advertencias se pueden suprimir exactamente para este escenario. Una vez que haya revisado su código y esté de acuerdo con esa evaluación, coloque esto encima de su método: " [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification="BrainSlugs83 said so.")]" - asegúrese de tener una using System.Diagnostics.CodeAnalysis;declaración " " en su bloque de usos.
BrainSlugs83
2
Eche un vistazo a: msdn.microsoft.com/en-us/library/…
Adil Mammadov

Respuestas:

-3

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

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

El código modificado funciona de la siguiente manera: las referencias se establecen en null, tan pronto como la responsabilidad de la eliminación se transfiere a otro objeto. Por ejemplo, memoryStreamse establece en nulldespués de que la llamada al CryptoStreamconstructor se haya realizado correctamente. cryptoStreamse establece en null, después de que la llamada al StreamWriterconstructor se haya realizado correctamente. Si no ocurre ninguna excepción, streamWriterse desecha en el finallybloque y, a su vez , se elimina CryptoStreamy MemoryStream.

Henrik
fuente
85
-1 Es realmente malo crear código feo solo para cumplir con una advertencia que debería suprimirse .
Jordão
4
Estoy de acuerdo en que no deberías sacrificar tu código para algo que podría terminar arreglado en algún momento en el futuro, solo suprímelo.
peteski
3
¿Cómo soluciona esto el problema? Aún se informa CA2202 porque memoryStream aún se puede eliminar dos veces en el bloque finalmente.
Chris Gessler
3
Dado que CryptoStream llama a Dispose en MemoryStream internamente, podría llamarse dos veces, que es el motivo de la advertencia. Probé tu solución y aún recibo la advertencia.
Chris Gessler
2
Oh cielos, tienes razón, no esperaba que hubiera una lógica de limpieza mezclada con tu ... lógica-lógica ... eso es simplemente extraño y críptico, es ciertamente inteligente, pero de nuevo, da miedo, no hagas esto en el código de producción; para ser claros: entiendes que no hay un problema funcional real que esto esté resolviendo, ¿correcto? (Está bien deshacerse de estos objetos varias veces). - Quitaría el voto negativo si pudiera (SO me lo impide, dice que tienes que editar la respuesta) - pero solo lo haría de mala gana ... - y en serio, nunca hagas esto.
BrainSlugs83
142

Debe suprimir las advertencias en este caso. El código que se ocupa de los desechables debe ser coherente y no debería tener que preocuparse de que otras clases se apropien de los desechables que haya creado y también los utilicen 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();
  }
}

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

Si se llama al método Dispose de un objeto 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 usingdeclaración con cordura en una cascada de desechables, como he mostrado anteriormente (o tal vez esto sea solo un efecto secundario agradable). De la misma manera, CA2202 no tiene ningún propósito útil, y debe suprimirse por proyecto. El verdadero culpable sería una implementación defectuosa de Dispose, y CA1065 debería encargarse de eso (si es bajo su responsabilidad).

Jordão
fuente
14
En mi opinión, esto es un error en fxcop, esta regla es simplemente incorrecta. El método dispose nunca debe lanzar una ObjectDisposedException y, si lo hace, debe tratarlo en ese momento presentando un error sobre el autor del código que implementa dispose de esta manera.
justin.m.chase
14
Estoy de acuerdo con @HansPassant en el otro hilo: la herramienta está haciendo su trabajo y le advierte de un detalle inesperado de implementación de las clases. Personalmente, creo que el verdadero problema es el diseño de las propias API. Tener las clases anidadas de forma predeterminada para suponer que está bien tomar posesión de otro objeto creado en otro lugar parece muy cuestionable. Puedo ver dónde podría ser útil si el objeto resultante fuera a ser devuelto, pero el incumplimiento de esa suposición parece contrario a la intuición, además de violar los patrones normales de IDisposable.
BTJ
8
Pero msdn no recomienda suprimir este tipo de mensaje. Eche un vistazo a: msdn.microsoft.com/en-us/library/…
Adil Mammadov
2
Gracias por el enlace @AdilMammadov, información útil, pero Microsoft no siempre tiene razón sobre estas cosas.
Tim Abell
40

Bueno, es preciso, el método Dispose () en estos flujos se llamará más de una vez. La clase StreamReader tomará la 'propiedad' del cryptoStream, por lo que eliminar streamWriter también eliminará cryptoStream. De manera similar, la clase CryptoStream asume la responsabilidad de memoryStream.

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

Hans Passant
fuente
10
Tener que tener un conocimiento especial sobre el comportamiento interno de las clases (como un desechable que se apropia de otro) es demasiado preguntar si uno quiere diseñar una API reutilizable. Entonces creo que las usingdeclaraciones deberían quedarse. Estas advertencias son realmente tontas.
Jordão
4
@ Jordão - ¿No es para eso la herramienta? ¿Para advertirle sobre comportamientos internos que quizás no conocía?
Hans Passant
8
Estoy de acuerdo. Pero, todavía no dejaría caer las usingdeclaraciones. Simplemente se siente mal confiar en otro objeto para deshacerse de un objeto que creé. Para este código, está bien, pero hay muchas implementaciones de Streamy TextWriterpor ahí (no solo en el BCL). El código para usarlos todos debe ser coherente.
Jordão
3
Sí, de acuerdo con Jordão. Si realmente desea que el programador conozca el comportamiento interno de la API, hable al nombrar su función como DoSomethingAndDisposeStream (Stream stream, OtherData data).
ZZZ
4
@HansPassant ¿Puede señalar dónde está documentado que el XmlDocument.Save()método llamará Disposeal parámetro proporcionado? No lo veo en la documentación de Save(XmlWriter)(donde estoy experimentando el error FxCop), o en el Save()método en sí, o en la documentación del XmlDocumentmismo.
Ian Boyd
9

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

Entonces, su MemoryStream es eliminado tanto por CryptoStream como por la declaración using . Y su CryptoStream es eliminado por StreamWriter y la declaración de uso externa .


Después de un poco de experimentación, parece imposible deshacerse completamente de las advertencias. Teóricamente, MemoryStream debe eliminarse, pero, en teoría, ya no podría acceder a su método ToArray. Prácticamente, no es necesario eliminar un MemoryStream, por lo que optaría por 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();
dtb
fuente
9

Haría esto usando #pragma warning disable.

Las Pautas de .NET Framework recomiendan implementar IDisposable.Dispose de tal manera que se pueda llamar varias veces. De la descripción de MSDN de IDisposable .

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

Por lo tanto, la advertencia parece no tener sentido:

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

Supongo que se podría argumentar 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 cuando usa clases de .NET Framework como lo está haciendo, diría que es seguro suprimir la advertencia usando 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 .

Joe
fuente
4
CA2202 es una advertencia de análisis de código y no una advertencia del compilador. #pragma warning disablesolo se puede utilizar para suprimir las advertencias del compilador. Para suprimir una advertencia de Análisis de código, debe utilizar un atributo.
Martin Liversage
2

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

Parece que todo el asunto CA2202 se activa porque MemoryStreamse 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 }

Tenga en cuenta que tenemos que devolver el memoryStreaminterior de la última usingdeclaración (línea 10) porque cryptoStreamse elimina en la línea 11 (porque se usa en la streamWriter usingdeclaración), lo que lleva memoryStreama obtener también en la línea 11 (porque memoryStreamse usa para crear cryptoStream).

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

EDITAR:

Por gracioso que parezca, descubrí que si reemplaza el GetMemoryStreammétodo 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.

Jimi
fuente
1

El cryptostream se basa en el memorystream.

Lo que parece estar sucediendo es que cuando se elimina el flujo criptográfico (al final del uso), el flujo de memoria también se elimina, entonces el flujo de memoria se elimina de nuevo.

Shiraz Bhaiji
fuente
1

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

Saqué 2 de los 3 flujos como campos y los eliminé en el Dispose()método de mi clase. Sí, la implementación de la IDisposableinterfaz puede no ser necesariamente lo que está buscando, pero la solución parece bastante limpia en comparación con las dispose()llamadas desde todos los lugares aleatorios del 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();
                }
            }
        }
   }
divyanshm
fuente
0

Fuera del tema, pero le sugiero que utilice una técnica de formato diferente para agrupar usings:

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 recomiendo usar vars aquí para evitar repeticiones de nombres de clases realmente largos.

PS Gracias a @ShellShock para señalar que no puedo omitir para los apoyos primero usingya que haría memoryStreamen la returndeclaración fuera del alcance.

Dan Abramov
fuente
5
¿MemoryStream.ToArray () no estará fuera de alcance?
Polyfun
Esto es absolutamente equivalente al código original. Simplemente omití las llaves, al igual que puedes hacerlo con ifs (aunque no recomendaría esta técnica para otra cosa que no sea usings).
Dan Abramov
2
En el código original, memoryStream.ToArray () estaba dentro del alcance del primer uso; lo tienes fuera del alcance.
Polyfun
Muchas gracias, me acabo de dar cuenta de que querías decir returndeclaración. Tan verdadero. Edité la respuesta para reflejar esto.
Dan Abramov
Personalmente, creo que usingsin llaves hace que el código sea más frágil (piense en años de diferencias y fusiones). joelonsoftware.com/2005/05/11/making-wrong-code-look-wrong & imperialviolet.org/2014/02/22/applebug.html
Tim Abell
0

¡Evite todos los usos y utilice Dispose-Calls anidados!

    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
fuente
1
Explique por qué debe evitarlo usingen este caso.
StuperUser
1
Puede mantener la instrucción using en el medio, pero debe resolver las otras. Para obtener una solución lógica coherente y actualizable en todas las direcciones, ¡decidí eliminar todos los usos!
Harry Saltzman
0

Solo quería desenvolver el código para que podamos ver múltiples llamadas a Disposelos 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 al 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 le advierte.

Tiene algunas opciones;

  • solo llame Disposeuna vez a cualquier objeto; no useusing
  • sigue llamando a dispose dos veces y espero que el código no se bloquee
  • suprime la advertencia
Ian Boyd
fuente
-1

Usé este tipo de código que toma bytes [] y devuelve bytes [] sin usar flujos

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 tiene que hacer es la conversión de cadena a byte [] usando codificaciones.

Luka Rahne
fuente