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.