O bloco de captura não está sendo avaliado quando exceções são lançadas de finalmente
Essa questão surgiu porque o código que funcionava anteriormente no .NET 4.0 falhou com uma exceção não tratada no .NET 4.5, parcialmente devido a try / finallys. Se você quiser detalhes, leia mais emConexão da Microsoft. Eu usei como base para este exemplo, então pode ser útil fazer referência.
O códigoPara as pessoas que escolheram não ler sobre os detalhes por trás dessa questão, aqui está uma rápida olhada nas condições em que isso aconteceu:
using(var ms = new MemoryStream(encryptedData))
using(var cryptoStream = new CryptoStream(encryptedData, decryptor, CryptoStreamMode.Read))
using(var sr = new StreamReader(cryptoStream))
Esta questão é que existem exceções lançadas a partir doDispose
método de CryptoStream (já que eles estão dentro de uma instrução using, estas exceções foram lançadas de dois blocos finally diferentes). QuandocryptoStream.Dispose()
é chamado peloStreamReader
, aCryptographicException
é lançado. A segunda vezcryptoStream.Dispose()
é chamado, em sua declaração de uso, lança umArgumentNullException
O código a seguir remove a maior parte do código desnecessário do link fornecido acima e desenrola as instruções using em try / finallys para mostrar claramente que elas estão sendo lançadas em blocos finais.
using System;
using System.Security.Cryptography;
namespace Sandbox
{
public class Program
{
public static void Main(string[] args)
{
try
{
try
{
try
{
Console.WriteLine("Propagate, my children");
}
finally
{
// F1
Console.WriteLine("Throwing CryptographicExecption");
throw new CryptographicException();
}
}
finally
{
// F2
Console.WriteLine("Throwing ArgumentException");
throw new ArgumentException();
}
}
catch (ArgumentException)
{
// C1
Console.WriteLine("Caught ArgumentException");
}
// Same behavior if this was in an enclosing try/catch
catch (CryptographicException)
{
// C2
Console.WriteLine("Caught CryptographicException");
}
Console.WriteLine("Made it out of the exception minefield");
}
}}
Nota: O try / finally corresponde ao expandido usando instruções do código referenciado.
Saída:
Propagate, my children Throwing CryptographicExecption Throwing ArgumentException Caught ArgumentException Press any key to continue . . .
Não parece que oCryptographicException
bloco de captura é sempre executado. No entanto, a remoção desse bloco catch faz com que a exceção finalize o tempo de execução.
EDIT: isto foi atualizado para a revisão mais recente da especificação. Aquela que eu peguei do MSDN tinha um texto mais antigo.Lost
foi atualizado paraterminated
.
Mergulhando na especificação C #, as seções 8.9.5 e 8.10 discutem o comportamento de exceção:
Quando uma exceção é lançada, incluindo de dentro de um bloco finally, o controle é transferido para a primeira cláusula catch em uma instrução try envolvente. Isso continua até tentar instruções até encontrar uma adequada.Se uma exceção é lançada durante a execução de um bloco finally, e uma exceção já estava sendo propagada,essa exceção é terminada"Terminado" faz parecer que a primeira exceção seria para sempre escondida pela segunda exceção, embora não pareça ser o que está acontecendo.
Tenho certeza que a questão está aqui em algum lugarNa maior parte, é fácil visualizar o que o tempo de execução está fazendo. O código é executado no primeiro bloco final (F1
) onde uma exceção é lançada. À medida que a exceção se propaga, a segunda exceção é lançada do segundo bloco finally (F2
).
De acordo com as especificações, oCryptographicException
jogado deF1
agora está terminado, e o tempo de execução está procurando por um manipulador para oArgumentException
. O tempo de execução localiza um manipulador e executa o código no bloco catch para oArgumentException
(C1
).
Aqui é onde fica nebuloso: a especificação diz que a primeira exceção seria encerrada. No entanto, se o segundo bloco catch (C2
) é removido do código, oCryptographicException
que foi supostamente perdido, é agora uma exceção não tratada que encerra o programa. Com oC2
presente, o código não terminará de uma exceção não tratada, portanto, na superfícieaparece estar lidando com a exceção, mas o código de manipulação de exceção no bloco nunca é executado.
As perguntas são basicamente as mesmas, mas reformuladas para especificidade.
Como é que oCryptographicException
torna-se terminado devido àArgumentException
exceção lançada do bloco finally final, removendo ocatch (CryptographicException)
block faz com que a exceção não seja tratada e encerre o tempo de execução?
Como o tempo de execução parece estar lidando comCryptographicException
quando ocatch (CryptographicException)
bloco está presente, por que o código dentro do bloco não está sendo executado?
Ainda estou olhando para o comportamento real disso, e muitas das respostas foram particularmente úteis para, pelo menos, responder a partes das perguntas acima.
Outro comportamento curioso, que acontece quando você executa o código com ocatch (CryptographicException)
block commented out, é a diferença entre o .NET 4.5 e o .NET 3.5. O .NET 4.5 lançará oCryptographicException
e encerrar o aplicativo. .NET 3.5, no entanto, parece se comportar mais de acordo com a especificação C # onde a exceção.
Propagate, my children Throwing CryptographicExecption Unhandled Exception: System.Security.Cryptography.CryptographicException [...] ram.cs:line 23 Throwing ArgumentException Caught ArgumentException Made it out of the exception minefield
No .NET 3.5, vejo o que li na especificação. A exceção se torna "perdida" ou "terminada", já que a única coisa que precisa ser pego é aArgumentException
. Por causa disso, o programa pode continuar a execução. Eu só tenho o .NET 4.5 na minha máquina, gostaria de saber se isso acontece no .NET 4.0?