Tratamento de exceções em C#

O tratamento de exceções é uma parte fundamental do desenvolvimento de software, garantindo que erros inesperados sejam tratados de maneira controlada e que a aplicação continue funcionando de forma estável. No C#, as exceções podem ser tratadas utilizando as estruturas “try”, “catch”, “finally” e “throw”. Neste artigo vamos explorar como utilizar essas estruturas para capturar, tratar e limpar recursos e como tratar exceções de forma global.

O que são exceções?

Exceções são condições anormais ou erros que ocorrem durante a execução de um programa. Elas podem ser causadas por uma variedade de razões, como falha em uma operação de leitura de arquivos, tentativa de divisão por zero, entre outros. O tratamento adequado dessas exceções é crucial para garantir que a aplicação não falhe silenciosamente e que o usuário receba uma resposta apropriada.

Bloco try

O bloco “try” é utilizado para encapsular código que pode gerar uma exceção. Se uma exceção ocorrer, a execução será desviada para o bloco “catch” correspondente (caso exista). Aqui está um exemplo de como usar o “try-catch” para tratar exceções:

				
					try
{
    var caminho = "D:\\Arquivo\\Inexistente\\arquivo.txt";
    var conteudo = File.ReadAllText(caminho);
    Console.WriteLine(conteudo);
}
catch (FileNotFoundException ex)
{
    Console.WriteLine($"Erro: Arquivo não encontrado - {ex.Message}");
}

				
			

Neste exemplo, qualquer tentativa de leitura de um arquivo inexistente gerará uma exceção que será capturada pelo bloco catch.

Sem o uso de “try-catch”, o programa falharia abruptamente se ocorresse uma exceção. Por exemplo, tente executar o código abaixo sem tratamento de exceção:

				
					var caminho = "D:\\Arquivo\\Inexistente\\arquivo.txt";
string conteudo = File.ReadAllText(caminho);
Console.WriteLine(conteudo);

				
			
Uma exceção de difícil compreensão será lançada, resultando em uma mensagem genérica de erro que pode não fornecer informações claras sobre a causa do problema. Isso dificulta o diagnóstico e a resolução do erro. Por outro lado, ao usar o “try-catch”, podemos capturar e tratar a exceção de forma controlada, personalizando a mensagem de erro para que ela seja mais informativa e fácil de compreender.

Bloco catch

Como vimos acima, o “catch” captura e trata exceções que foram lançadas em um bloco “try”. Você pode definir múltiplos blocos “catch” para tratar tipos específicos de exceções.

				
					try
{
    int divisor = 0;
    int resultado = 100 / divisor;
    Console.WriteLine($"Resultado: {resultado}");
}
catch (DivideByZeroException)
{
    Console.WriteLine("Erro: Não é possível dividir por zero.");
}
catch (Exception)
{
    Console.WriteLine("Ocorreu um erro");
}

				
			

Neste exemplo, se “divisor” for zero, a exceção “DivideByZeroException” será capturada. Outros erros serão tratados pelo bloco genérico “catch”. Aqui a ordem de definição dos blocos catch é importante: exceções mais específicas devem ser tratadas primeiro, enquanto as mais genéricas, como a classe “Exception”, deve vir depois. 

Ainda no bloco “catch” é importante notar que você pode ou não declarar uma variável para receber os detalhes da exceção. Note que no exemplo acima especificamos apenas o nome da classe de exceção que está sendo tratada. Dessa forma, capturamos a exceção, mas não temos acesso aos seus dados. Se desejarmos acessar maiores detalhes sobre o erro, podemos fazer da seguinte forma:

				
					catch (Exception ex)
{
    Console.WriteLine($"Erro genérico: {ex.Message}");
}

				
			

Agora a variável “ex” recebe a instância da exceção lançada e, a partir dela, podemos acessar os detalhes do erro. Aqui cabe destacar as propriedades Message e StackTrace das classes de exceção. A primeira traz uma mensagem descritiva sobre o problema, enquanto a segunda traz o detalhamento da pilha de chamadas no código que gerou a exceção. Essa informação é crucial para identificarmos qual trecho do código causou o erro.

Também é possível adicionar filtros no bloco “catch” para realizar tratamentos mais personalizados para cada cenário. Por exemplo:

				
					public int GetItem(int index)
	{
		List<int> items = [1, 2, 3, 4, 5];
		try
		{
			return items[index];
		}
		catch(ArgumentOutOfRangeException) when (index < 0)
		{
			Console.WriteLine("Índice não pode ser negativo");
			throw;
		}
		catch(ArgumentOutOfRangeException)
		{
			Console.WriteLine($"Índice incorreto. A lista só contém {items.Count} itens.");
			throw;
		}
	}

				
			

No código acima estamos tratando o mesmo tipo de exceção duas vezes. Porém, no primeiro caso adicionamos um filtro para que o bloco “catch” só seja utilizado quando o parâmetro “index” for menor que zero. Para quaisquer outros valores inválidos de “index”, o segundo bloco “catch” será acionado.

Bloco finally

O bloco “finally” é utilizado para garantir que o código de limpeza será executado, independentemente de uma exceção ter sido lançada ou não. Ele pode ser usado com ou sem o bloco “catch” e é muito comum para realizar o descarte de recursos após algum processamento.

				
					public void AbrirConexao()
{
    SqlConnection conexao = null;
    try
    {
        conexao = new SqlConnection("sua_conexao");
        conexao.Open();
    }
    catch (SqlException ex)
    {
        Console.WriteLine($"Erro de SQL: {ex.Message}");
    }
    finally
    {
        if (conexao != null)
        {
            conexao.Close();
            Console.WriteLine("Conexão fechada.");
        }
    }
}

				
			
No código acima o bloco “finally” fecha a conexão com o banco de dados, mesmo que ocorra uma exceção durante a execução.

Instrução throw

A instrução “throw” é utilizada para lançar uma exceção, seja criando uma nova ou re-lançando uma exceção já capturada. Isso permite que a exceção seja tratada em um nível superior ou forneça informações mais detalhadas sobre o erro.

Por exemplo, no código abaixo, se a idade for menor que 18, uma exceção “ArgumentException” é lançada:

				
					public void ValidarIdade(int idade)
{
    if (idade < 18)
    {
        throw new ArgumentException("A idade deve ser maior ou igual a 18 anos.");
    }
}

				
			

Além disso, o “throw” pode ser usado dentro de um bloco “catch” para re-lançar uma exceção capturada, permitindo que ela seja tratada em outro nível, como mostrado abaixo:

				
					try
{
     if (idade < 18)
    {
        throw new ArgumentException("A idade deve ser maior ou igual a 18 anos.");
    }
}
catch (Exception ex)
{
    Console.WriteLine($"Log de erro: {ex.Message}");
    throw; 
}

				
			

Esse uso do “throw” ajuda a criar um fluxo de controle eficiente para exceções, possibilitando um tratamento mais flexível e organizado.

Acelere a sua carreira conosco!

Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET StartClique aqui
Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça a Mentoria .NET ExpertClique aqui

Conclusão

Adotar um tratamento de exceções eficiente não apenas melhora a qualidade do software, mas também oferece um nível superior de controle e previsibilidade, possibilitando que os desenvolvedores se antecipem a problemas e entreguem soluções que resistem às complexidades do mundo real.