Usar exceções
No C#, os erros no programa em tempo de execução são propagados pelo programa usando um mecanismo chamado exceções. As exceções são geradas pelo código que encontra um erro e capturadas pelo código que pode corrigir o erro. As exceções podem ser geradas pelo tempo de execução do .NET ou pelo código em um programa. Uma vez que uma exceção é gerada, ela é propagada acima na pilha de chamadas até uma instrução catch
para a exceção ser encontrada. As exceções não capturadas são tratadas por um manipulador de exceção genérico fornecido pelo sistema que exibe uma caixa de diálogo.
As exceções são representadas por classes derivadas de Exception. Essa classe identifica o tipo de exceção e contém propriedades que têm detalhes sobre a exceção. Gerar uma exceção envolve criar uma instância de uma classe derivada de exceção, opcionalmente configurar propriedades da exceção e, em seguida, gerar o objeto usando a palavra-chave throw
. Por exemplo:
class CustomException : Exception
{
public CustomException(string message)
{
}
}
private static void TestThrow()
{
throw new CustomException("Custom exception in TestThrow()");
}
Depois que uma exceção é gerada, o runtime verifica a instrução atual para ver se ela está dentro de um bloco try
. Se estiver, todos os blocos catch
associados ao bloco try
serão verificados para ver se eles podem capturar a exceção. Os blocos Catch
normalmente especificam os tipos de exceção. Se o tipo do bloco catch
for do mesmo tipo que a exceção ou uma classe base da exceção, o bloco catch
poderá manipular o método. Por exemplo:
try
{
TestThrow();
}
catch (CustomException ex)
{
System.Console.WriteLine(ex.ToString());
}
Se a instrução que gera uma exceção não estiver dentro de um bloco try
ou se o bloco try
que o contém não tiver um bloco catch
correspondente, o runtime verificará o método de chamada quanto a uma instrução try
e blocos catch
. O runtime continuará acima na pilha de chamada, pesquisando um bloco catch
compatível. Depois que o bloco catch
for localizado e executado, o controle será passado para a próxima instrução após aquele bloco catch
.
Uma instrução try
pode conter mais de um bloco catch
. A primeira instrução catch
que pode manipular a exceção é executado, todas as instruções catch
posteriores, mesmo se forem compatíveis, são ignoradas. Ordenar blocos catch do mais específico (ou mais derivado) para o menos específico. Por exemplo:
using System;
using System.IO;
namespace Exceptions
{
public class CatchOrder
{
public static void Main()
{
try
{
using (var sw = new StreamWriter("./test.txt"))
{
sw.WriteLine("Hello");
}
}
// Put the more specific exceptions first.
catch (DirectoryNotFoundException ex)
{
Console.WriteLine(ex);
}
catch (FileNotFoundException ex)
{
Console.WriteLine(ex);
}
// Put the least specific exception last.
catch (IOException ex)
{
Console.WriteLine(ex);
}
Console.WriteLine("Done");
}
}
}
Antes de o bloco catch
ser executado, o runtime verifica se há blocos finally
. Os blocos Finally
permitem que o programador limpe qualquer estado ambíguo que pode ser deixado de um bloco try
cancelado ou libere quaisquer recursos externos (como identificadores de gráfico, conexões de banco de dados ou fluxos de arquivo) sem esperar o coletor de lixo no runtime finalizar os objetos. Por exemplo:
static void TestFinally()
{
FileStream? file = null;
//Change the path to something that works on your machine.
FileInfo fileInfo = new System.IO.FileInfo("./file.txt");
try
{
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
}
finally
{
// Closing the file allows you to reopen it immediately - otherwise IOException is thrown.
file?.Close();
}
try
{
file = fileInfo.OpenWrite();
Console.WriteLine("OpenWrite() succeeded");
}
catch (IOException)
{
Console.WriteLine("OpenWrite() failed");
}
}
Se WriteByte()
gerou uma exceção, o código no segundo bloco try
que tentar reabrir o arquivo falhará se file.Close()
não for chamado e o arquivo permanecerá bloqueado. Como os blocos finally
são executados mesmo se uma exceção for gerada, o bloco finally
no exemplo anterior permite que o arquivo seja fechado corretamente e ajuda a evitar um erro.
Se não for encontrado nenhum bloco catch
compatível na pilha de chamadas após uma exceção ser gerada, ocorrerá uma das três coisas:
- Se a exceção estiver em um finalizador, o finalizador será abortado e o finalizador base, se houver, será chamado.
- Se a pilha de chamadas contiver um construtor estático ou um inicializador de campo estático, uma TypeInitializationException será gerada, com a exceção original atribuída à propriedade InnerException da nova exceção.
- Se o início do thread for atingido, o thread será encerrado.