CA2202, como resolver este caso

102

Alguém pode me dizer como remover todos os avisos CA2202 do código a seguir?

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

Aviso 7 CA2202: Microsoft.Usage: O objeto 'cryptoStream' pode ser descartado mais de uma vez no método 'CryptoServices.Encrypt (string, byte [], byte [])'. Para evitar a geração de uma System.ObjectDisposedException, você não deve chamar Dispose mais de uma vez em um objeto .: Lines: 34

Aviso 8 CA2202: Microsoft.Usage: O objeto 'memoryStream' pode ser descartado mais de uma vez no método 'CryptoServices.Encrypt (string, byte [], byte [])'. Para evitar a geração de System.ObjectDisposedException, você não deve chamar Dispose mais de uma vez em um objeto .: Linhas: 34, 37

Você precisa do Visual Studio Code Analysis para ver esses avisos (não são avisos do compilador c #).

testalino
fonte
1
Este código não gera esses avisos.
Julien Hoarau
1
Recebo 0 avisos para isso (Avisar nível 4, VS2010). E para alguém pesquisando problemas nesta área, por favor, adicione o texto das advertências também.
Henk Holterman
29
Os avisos CAxxxx são gerados pela Análise de código e FxCop.
dtb
Este aviso não se aplica ao código mostrado - os avisos podem ser suprimidos exatamente para este cenário. Depois de revisar seu código e concordar com essa avaliação, coloque isto acima de seu método: " [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification="BrainSlugs83 said so.")]" - certifique-se de ter uma using System.Diagnostics.CodeAnalysis;declaração " " em seu bloco de uso.
BrainSlugs83
2
Dê uma olhada em: msdn.microsoft.com/en-us/library/…
Adil Mammadov

Respostas:

-3

Compila sem 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 em resposta aos comentários: Acabei de verificar novamente que este código não gera o aviso, enquanto o original sim. No código original, CryptoStream.Dispose()e MemoryStream().Dispose() são chamados duas vezes (o que pode ou não ser um problema).

O código modificado funciona da seguinte maneira: as referências são definidas nullcomo, assim que a responsabilidade pelo descarte é transferida para outro objeto. Por exemplo, memoryStreamé definido como nullapós a chamada ao CryptoStreamconstrutor ser bem-sucedida. cryptoStreamestá configurado paranull , após a chamada ao StreamWriterconstrutor ser bem-sucedida. Se nenhuma exceção ocorrer, streamWriteré descartado no finallybloco e, por sua vez, descartará CryptoStreame MemoryStream.

Henrik
fonte
85
-1 É muito ruim criar um código feio apenas para obedecer a um aviso que deve ser suprimido .
Jordão
4
Eu concordo que você não deve destruir seu código para algo que pode acabar corrigido em algum ponto no futuro, apenas suprimir.
peteski
3
Como isso resolve o problema? CA2202 ainda é relatado porque memoryStream ainda pode ser descartado duas vezes no bloco finally.
Chris Gessler
3
Como o CryptoStream chama Dispose no MemoryStream internamente, ele poderia ser chamado duas vezes, o que é o motivo do aviso. Tentei sua solução e ainda recebo o aviso.
Chris Gessler
2
Oh caramba, você está correto - Eu não esperava que houvesse lógica de limpeza misturada com sua ... lógica ... - isso é simplesmente bizarro e enigmático - é certamente inteligente - mas, novamente, assustador - por favor, não faça isso no código de produção; para ser claro: você entende que não há nenhum problema funcional real que isso esteja resolvendo, correto? (Não há problema em descartar esses objetos várias vezes.) - Eu removeria o voto negativo se pudesse (SO me impede, diz que você tem que editar a resposta) - mas eu só faria isso com relutância ... - e sério, nunca faça isso.
BrainSlugs83
142

Você deve suprimir os avisos neste caso. O código que lida com descartáveis ​​deve ser consistente, e você não deve se preocupar se outras classes se apropriam dos descartáveis ​​que você criou e também recorrem Disposea eles.

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

ATUALIZAÇÃO: Na documentação IDisposable.Dispose você pode ler isto:

Se o método Dispose de um objeto for chamado mais de uma vez, o objeto deve ignorar todas as chamadas após a primeira. O objeto não deve lançar uma exceção se seu método Dispose for chamado várias vezes.

Pode-se argumentar que essa regra existe para que os desenvolvedores possam empregar a usinginstrução de maneira saudável em uma cascata de descartáveis, como mostrei acima (ou talvez este seja apenas um bom efeito colateral). Da mesma forma, então, CA2202 não tem nenhum propósito útil e deve ser suprimido em termos de projeto. O verdadeiro culpado seria uma implementação incorreta do Dispose, e o CA1065 deve cuidar disso (se estiver sob sua responsabilidade).

Jordão
fonte
14
Na minha opinião, este é um bug no fxcop, esta regra está simplesmente errada. O método dispose nunca deve lançar uma ObjectDisposedException e, se o fizer, você deve lidar com isso naquele momento, registrando um bug no autor do código que implementa o dispose dessa maneira.
justin.m.chase 01 de
14
Concordo com @HansPassant no outro segmento: a ferramenta está fazendo seu trabalho e avisando sobre um detalhe de implementação inesperado das classes. Pessoalmente, acho que o verdadeiro problema é o design das próprias APIs. Ter as classes aninhadas assumindo que não há problema em se apropriar de outro objeto criado em outro lugar parece altamente questionável. Posso ver onde isso poderia ser útil se o objeto resultante fosse ser retornado, mas padronizar para essa suposição parece contra-intuitivo, além de violar os padrões normais de ID descartáveis.
BTJ
8
Mas o msdn não recomenda suprimir este tipo de mensagem. Dê uma olhada em: msdn.microsoft.com/en-us/library/…
Adil Mammadov
2
Obrigado pelo link @AdilMammadov, informações úteis, mas a microsoft nem sempre tem razão sobre essas coisas.
Tim Abell
40

Bem, é preciso, o método Dispose () nesses fluxos será chamado mais de uma vez. A classe StreamReader terá 'propriedade' de cryptoStream, portanto, descartar streamWriter também descartará cryptoStream. Da mesma forma, a classe CryptoStream assume a responsabilidade pelo memoryStream.

Esses bugs não são exatamente reais, essas classes .NET são resilientes a várias chamadas Dispose (). Mas se você quiser se livrar do aviso, você deve descartar a instrução using para esses objetos. E se preocupe um pouco ao raciocinar o que acontecerá se o código lançar uma exceção. Ou feche o aviso com um atributo. Ou simplesmente ignore o aviso, pois é bobo.

Hans Passant
fonte
10
Ter que ter conhecimento especial sobre o comportamento interno das classes (como um descartável assumindo a propriedade de outra) é pedir muito se alguém deseja projetar uma API reutilizável. Então eu acho que as usingafirmações deveriam ficar. Esses avisos são realmente bobos.
Jordão
4
@ Jordão - não é pra isso que serve a ferramenta? Para avisá-lo sobre o comportamento interno que você talvez não conheça?
Hans Passant
8
Concordo. Mas, eu ainda não largaria as usingdeclarações. Parece errado confiar em outro objeto para descartar um objeto que eu criei. Para este código, está tudo bem, mas existem muitas implementações de Streame TextWriterpor aí (não apenas no BCL). O código para usar todos eles deve ser consistente.
Jordão
3
Sim, concordo com Jordão. Se você realmente deseja que o programador esteja ciente do comportamento interno da API, então fale nomeando sua função como DoSomethingAndDisposeStream (Stream stream, OtherData data).
ZZZ
4
@HansPassant Você pode apontar onde está documentado que o XmlDocument.Save()método irá chamar Disposeo parâmetro fornecido? Eu não vejo isso na documentação do Save(XmlWriter)(onde estou enfrentando o bug FxCop), ou no Save()próprio método, ou na documentação dele XmlDocumentmesmo.
Ian Boyd
9

Quando um StreamWriter é descartado, ele automaticamente descarta o Stream encapsulado (aqui: o CryptoStream ). CryptoStream também descarta automaticamente o Stream empacotado (aqui: o MemoryStream ).

Portanto, seu MemoryStream é descartado tanto pelo CryptoStream quanto pela instrução using . E seu CryptoStream é descartado pelo StreamWriter e pela instrução externa using .


Depois de alguma experimentação, parece ser impossível se livrar completamente dos avisos. Teoricamente, o MemoryStream precisa ser descartado, mas teoricamente você não pode mais acessar seu método ToArray. Praticamente, um MemoryStream não precisa ser descartado, então eu escolheria esta solução e suprimiria o aviso 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
fonte
9

Eu faria isso usando #pragma warning disable.

As diretrizes do .NET Framework recomendam implementar IDisposable.Dispose de forma que possa ser chamado várias vezes. Da descrição do MSDN de IDisposable.Dispose :

O objeto não deve lançar uma exceção se seu método Dispose for chamado várias vezes

Portanto, o aviso parece quase sem sentido:

Para evitar a geração de uma System.ObjectDisposedException, você não deve chamar Dispose mais de uma vez em um objeto

Acho que pode-se argumentar que o aviso pode ser útil se você estiver usando um objeto IDisposable mal implementado que não segue as diretrizes de implementação padrão. Mas ao usar classes do .NET Framework como você está fazendo, eu diria que é seguro suprimir o aviso usando um #pragma. E, IMHO, isso é preferível a passar por obstáculos, conforme sugerido na documentação do MSDN para este aviso .

Joe
fonte
4
CA2202 é um aviso de análise de código e não um aviso do compilador. #pragma warning disablesó pode ser usado para suprimir avisos do compilador. Para suprimir um aviso de análise de código, você precisa usar um atributo.
Martin Liversage
2

Eu me deparei com problemas semelhantes em meu código.

Parece que todo o CA2202 é acionado porque MemoryStream pode ser descartado se ocorrer uma exceção no construtor (CA2000).

Isso poderia ser resolvido assim:

 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 temos que retornar o memoryStreaminterior da última usinginstrução (linha 10) porque cryptoStreamé descartado na linha 11 (porque é usado na streamWriter usinginstrução), o que leva memoryStreama também ser descartado na linha 11 (porque memoryStreamé usado para criar ocryptoStream ).

Pelo menos esse código funcionou para mim.

EDITAR:

Por mais engraçado que pareça, descobri que se você substituir o GetMemoryStreammétodo pelo código a seguir,

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

você obtém o mesmo resultado.

Jimi
fonte
1

O cryptostream é baseado no fluxo de memória.

O que parece estar acontecendo é que quando o crypostream é descartado (no final do uso), o fluxo de memória também é descartado, então o fluxo de memória é descartado novamente.

Shiraz Bhaiji
fonte
1

Eu queria resolver isso da maneira certa - ou seja, sem suprimir os avisos e descartar corretamente todos os objetos descartáveis.

Retirei 2 dos 3 streams como campos e os dispensei no Dispose()método da minha classe. Sim, implementar a IDisposableinterface pode não ser necessariamente o que você está procurando, mas a solução parece bem limpa em comparação com as dispose()chamadas de todos os lugares aleatórios no 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
fonte
0

Fora do tópico, mas eu sugiro que você use uma técnica de formatação 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();
}

Também defendo o uso de vars aqui para evitar repetições de nomes de classe muito longos.

PS: Obrigado a @ShellShock por apontar que não posso omitir os colchetes primeiro, usingpois isso faria memoryStreamem uma returndeclaração fora do escopo.

Dan Abramov
fonte
5
O memoryStream.ToArray () não estará fora do escopo?
Polyfun
Isso é absolutamente equivalente à parte original do código. Eu apenas omiti colchetes, da mesma forma que você pode fazer com ifs (embora eu não recomende essa técnica para outra coisa senão usings).
Dan Abramov
2
No código original, memoryStream.ToArray () estava dentro do escopo do primeiro uso; você tem isso fora do escopo.
Polyfun
Muito obrigado, acabei de perceber que você quis dizer returndeclaração. Tão verdade. Eu editei a resposta para refletir isso.
Dan Abramov
Eu pessoalmente acho que o usingsem chaves torna o código mais frágil (pense em anos de diferenças e mesclagens). joelonsoftware.com/2005/05/11/making-wrong-code-look-wrong & imperialviolet.org/2014/02/22/applebug.html
Tim Abell
0

Evite todos os usos e use Dispose-Calls aninhadas!

    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
fonte
1
Explique por que você deve evitar usingneste caso.
StuperUser
1
Você poderia manter a instrução using no meio, mas terá que resolver as outras. Para obter uma solução lógica coerente e em todas as direções atualizável, decidi remover todos os usos!
Harry Saltzman,
0

Eu só queria desembrulhar o código para que possamos ver várias chamadas para Disposenos 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

Embora a maioria das classes .NET sejam (esperançosamente) resilientes contra o erro de várias chamadas para .Dispose, nem todas as classes são tão defensivas contra o uso indevido do programador.

FX Cop sabe disso e avisa você.

Você tem poucas escolhas;

  • chame apenas Disposeuma vez em qualquer objeto; não useusing
  • continue chamando dispose duas vezes e espero que o código não trave
  • suprimir o aviso
Ian Boyd
fonte
-1

Eu usei esse tipo de código que pega byte [] e retorna byte [] sem 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);
}

Desta forma, tudo o que você precisa fazer é converter de string em byte [] usando codificações.

Luka Rahne
fonte