web-dev-qa-db-fra.com

CA2202, comment résoudre ce cas

Quelqu'un peut-il me dire comment supprimer tous les avertissements CA2202 du code suivant?

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

Avertissement 7 CA2202: Microsoft.Usage: l'objet 'cryptoStream' peut être supprimé plusieurs fois dans la méthode 'CryptoServices.Encrypt (chaîne, octet [], octet [])'. Pour éviter de générer une System.ObjectDisposedException, vous ne devez pas appeler Dispose plus d'une fois sur un objet.: Lines: 34

Avertissement 8 CA2202: Microsoft.Usage: l'objet 'memoryStream' peut être supprimé plusieurs fois dans la méthode 'CryptoServices.Encrypt (chaîne, octet [], octet [])'. Pour éviter de générer une System.ObjectDisposedException, vous ne devez pas appeler Dispose plus d'une fois sur un objet .: Lignes: 34, 37

Vous avez besoin de Visual Studio Code Analysis pour voir ces avertissements (ce ne sont pas des avertissements du compilateur c #).

100
testalino

Vous devez supprimer les avertissements dans ce cas. Le code qui traite des objets jetables doit être cohérent, et vous ne devriez pas avoir à vous soucier que d'autres classes s'approprient les objets jetables que vous avez créés et appellent également Dispose dessus.

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

MISE À JOUR: Dans la documentation IDisposable.Dispose vous pouvez lire ceci:

Si la méthode Dispose d'un objet est appelée plusieurs fois, l'objet doit ignorer tous les appels après le premier. L'objet ne doit pas lever d'exception si sa méthode Dispose est appelée plusieurs fois.

On peut faire valoir que cette règle existe pour que les développeurs puissent utiliser la déclaration using en toute sécurité dans une cascade de produits jetables, comme je l'ai montré ci-dessus (ou peut-être que c'est juste un effet secondaire agréable). De la même façon, CA2202 ne sert à rien et doit être supprimé par projet. Le vrai coupable serait une implémentation défectueuse de Dispose, et CA1065 devrait s'en occuper (si c'est sous votre responsabilité).

135
Jordão

Eh bien, c'est exact, la méthode Dispose () sur ces flux sera appelée plus d'une fois. La classe StreamReader s'appropriera le cryptoStream, donc l'élimination de streamWriter supprimera également le cryptoStream. De même, la classe CryptoStream prend la responsabilité du memoryStream.

Ce ne sont pas exactement de vrais bugs, ces classes .NET sont résistantes à plusieurs appels Dispose (). Mais si vous souhaitez vous débarrasser de l'avertissement, vous devez supprimer l'instruction using pour ces objets. Et faites-vous un peu mal en raisonnant ce qui se passera si le code lève une exception. Ou fermez l'avertissement avec un attribut. Ou ignorez simplement l'avertissement, car c'est idiot.

41
Hans Passant

Lorsqu'un StreamWriter est supprimé, il supprime automatiquement le Stream encapsulé (ici: le CryptoStream ). CryptoStream supprime également automatiquement le Stream encapsulé (ici: le MemoryStream ).

Ainsi, votre MemoryStream est éliminé à la fois par les instructions CryptoStream et à l'aide de . Et votre CryptoStream est éliminé par les instructions StreamWriter et externe à l'aide de .


Après quelques expérimentations, il semble impossible de se débarrasser complètement des avertissements. Théoriquement, le MemoryStream doit être supprimé, mais vous ne pouvez théoriquement plus accéder à sa méthode ToArray. Pratiquement, un MemoryStream n'a pas besoin d'être éliminé, donc j'irais avec cette solution et supprimerais l'avertissement 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
dtb

Je le ferais en utilisant #pragma warning disable.

Les directives du .NET Framework recommandent d'implémenter IDisposable.Dispose de telle manière qu'il puisse être appelé plusieurs fois. De la description MSDN de IDisposable.Dispose :

L'objet ne doit pas lever d'exception si sa méthode Dispose est appelée plusieurs fois

Par conséquent, l'avertissement semble presque vide de sens:

Pour éviter de générer une System.ObjectDisposedException, vous ne devez pas appeler Dispose plus d'une fois sur un objet

Je suppose que l'on pourrait soutenir que l'avertissement peut être utile si vous utilisez un objet IDisposable mal implémenté qui ne suit pas les directives d'implémentation standard. Mais lorsque vous utilisez des classes du .NET Framework comme vous le faites, je dirais qu'il est sûr de supprimer l'avertissement à l'aide d'un #pragma. Et à mon humble avis, il est préférable de passer par des cercles comme suggéré dans la documentation MSDN pour cet avertissement .

9
Joe

J'étais confronté à des problèmes similaires dans mon code.

On dirait que tout CA2202 est déclenché car MemoryStream peut être supprimé si une exception se produit dans le constructeur (CA2000).

Cela pourrait être résolu comme ceci:

 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 }

Notez que nous devons renvoyer le memoryStream à l'intérieur de la dernière instruction using (ligne 10) car cryptoStream est supprimé à la ligne 11 (car il est utilisé dans streamWriterusing), ce qui conduit memoryStream à être également supprimé à la ligne 11 (car memoryStream est utilisé pour créer le cryptoStream).

Au moins, ce code a fonctionné pour moi.

MODIFIER:

Aussi drôle que cela puisse paraître, j'ai découvert que si vous remplacez la méthode GetMemoryStream par le code suivant,

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

vous obtenez le même résultat.

2
Jimi

Le cryptostream est basé sur le memorystream.

Ce qui semble se produire, c'est que lorsque le flux cryogénique est disposé (à la fin de l'utilisation), le flux mémoire est également supprimé, puis le flux mémoire est à nouveau supprimé.

1
Shiraz Bhaiji

Je voulais résoudre ce problème de la bonne façon - c'est-à-dire sans supprimer les avertissements et éliminer correctement tous les objets jetables.

J'ai retiré 2 des 3 flux en tant que champs et les ai déposés dans la méthode Dispose() de ma classe. Oui, l'implémentation de l'interface IDisposable n'est pas nécessairement ce que vous recherchez, mais la solution semble assez propre par rapport aux appels dispose() de tous les endroits aléatoires du code.

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

Évitez tous les usages et utilisez les appels Dispose imbriqués!

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

Je voulais juste déballer le code afin que nous puissions voir plusieurs appels à Dispose sur les objets:

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

Alors que la plupart des classes .NET résistent (espérons-le) à l'erreur de plusieurs appels à .Dispose, pas toutes les classes sont aussi défensives contre une mauvaise utilisation du programmeur.

FX Cop le sait et vous avertit.

Vous avez quelques choix;

  • n'appeler Dispose qu'une seule fois sur n'importe quel objet; n'utilisez pas using
  • continuez d'appeler dispose de deux fois, et j'espère que le code ne plante pas
  • supprimer l'avertissement
0
Ian Boyd

Hors sujet, mais je vous suggère d'utiliser une technique de formatage différente pour regrouper les 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();
}

Je préconise également d'utiliser vars ici pour éviter les répétitions de noms de classe très longs.

P.S. Merci à @Shellshock d'avoir souligné que je ne peux pas omettre les accolades pour le premier using car cela ferait memoryStream dans return instruction hors de la portée.

0
Dan Abramov