C# - Entendendo o padrão Try
   Neste artigo vamos entender o motivo da existência do Try Pattern, quando utilizá-lo, suas vantagens e como ele torna o código mais eficiente e legível.

Quem trabalha com C# certamente já encontrou métodos como TryParse, TryGetValue, TryAdd e TryDequeue.

Embora pareçam apenas métodos com nomes semelhantes, todos seguem uma convenção muito importante da plataforma .NET conhecida como Try Pattern. Esse padrão foi criado para representar operações que podem falhar durante a execução como parte do fluxo normal da aplicação, sem que isso seja considerado uma condição excepcional.

 

O que é o padrão Try?

Na plataforma .NET existe uma convenção bastante utilizada para métodos cujo nome começa com o prefixo Try.
Esses métodos possuem algumas características em comum:

- retornam um valor bool;
- retornam true quando a operação é concluída com sucesso;
- retornam false quando a operação não pode ser realizada;
- disponibilizam o resultado através de um parâmetro out;
- evitam lançar exceções em situações consideradas esperadas.

De forma simplificada, a assinatura costuma seguir este modelo:


public static bool TryOperacao(Entrada entrada,out Resultado resultado)

Observe que o valor de retorno não representa o resultado da operação propriamente dito.
O valor booleano apenas informa se a operação foi bem-sucedida.
O resultado efetivo é retornado pelo parâmetro out.

Por que esse padrão existe?

Uma exceção representa uma situação anormal da aplicação. Entretanto, muitas operações podem falhar naturalmente durante o funcionamento do sistema. Alguns exemplos:

- um usuário digita um texto onde deveria informar um número;
- uma chave não existe em um dicionário;
- uma fila está vazia;
- um item já existe em uma coleção.

Nenhuma dessas situações caracteriza necessariamente um erro da aplicação. São apenas condições que precisam ser tratadas. Lançar exceções nesses cenários aumentaria o custo da execução e produziria um código mais complexo. Por esse motivo, a biblioteca base do .NET adotou o padrão Try.

1. TryParse

Provavelmente o método mais conhecido dessa família é o TryParse. Ele tenta converter uma representação textual para outro tipo de dado.

Sem TryParse:
 
string texto = "123";
int numero = int.Parse(texto);

Esse código funciona enquanto a entrada for válida. Entretanto, basta o usuário informar um valor diferente para que uma exceção seja lançada.
 
string texto = "ABC";
int numero = int.Parse(texto); // FormatException

Agora será necessário utilizar tratamento de exceções:
 
try
{
    int numero = int.Parse(texto);
}
catch (FormatException)
{
    Console.WriteLine("Número inválido.");
}

Embora correto, esse código utiliza exceções para tratar uma situação perfeitamente esperada.

Utilizando TryParse:
 
string texto = "123";

if (int.TryParse(texto, out int numero))
{
    Console.WriteLine(numero);
}
else
{
    Console.WriteLine("Valor inválido.");
}

Caso a conversão falhe, nenhuma exceção será lançada. O método simplesmente retorna false.

Outro exemplo:
 
Console.Write("Informe sua idade: ");

string entrada = Console.ReadLine()!;

if (!int.TryParse(entrada, out int idade))
{
    Console.WriteLine("Idade inválida.");
    return;
}

Console.WriteLine($"Idade informada: {idade}");

Esse padrão é muito utilizado em aplicações Web, APIs, aplicações desktop e sistemas de linha de comando.

2. TryGetValue

Ao trabalhar com Dictionary<TKey,TValue>, muitos desenvolvedores iniciantes escrevem:
 
if (clientes.ContainsKey(id))
{
    Cliente cliente = clientes[id];
}

Embora funcione, essa abordagem realiza duas operações de busca. Primeiro verifica se a chave existe, e depois procura novamente o valor correspondente.

A forma recomendada é utilizar TryGetValue:
 
if (clientes.TryGetValue(id, out Cliente? cliente))
{
    Console.WriteLine(cliente.Nome);
}
else
{
    Console.WriteLine("Cliente não encontrado.");
}

Além de produzir um código mais simples, essa abordagem realiza apenas uma busca.

Exemplo completo:
 
var capitais = new Dictionary<string, string>
{
    ["SP"] = "São Paulo",
    ["RJ"] = "Rio de Janeiro",
    ["MG"] = "Belo Horizonte"
};

if (capitais.TryGetValue("SP", out string? capital))
{
    Console.WriteLine(capital);
}
else
{
    Console.WriteLine("Estado não localizado.");
}

3. TryAdd

Até o .NET Core 2.0 era comum encontrar este código:
 
if (!usuarios.ContainsKey(login))
{
    usuarios.Add(login, usuario);
}

Hoje existe uma alternativa melhor. Caso a chave já exista, nenhuma exceção será lançada:
 
bool adicionou = usuarios.TryAdd(login, usuario);

if (!usuarios.TryAdd(login, usuario))
{
    Console.WriteLine("Usuário já cadastrado.");
}

Além de simplificar o código, essa abordagem elimina a dupla verificação. Inclusive existe uma regra oficial do analisador de código da plataforma (.NET Analyzer CA1864) recomendando substituir o padrão ContainsKey seguido de Add por TryAdd, justamente para evitar uma pesquisa desnecessária na coleção.

4. TryDequeue

Em filas concorrentes (ConcurrentQueue<T>), remover um elemento pode falhar porque outra thread já consumiu todos os itens. Nesse cenário, lançar exceções continuamente seria inadequado. Por isso existe TryDequeue:
 
ConcurrentQueue<string> fila = new();

fila.Enqueue("Pedido 1");
fila.Enqueue("Pedido 2");

while (fila.TryDequeue(out string? pedido))
{
    Console.WriteLine(pedido);
}

Quando a fila fica vazia, o método simplesmente retorna false. Esse comportamento torna o código muito mais apropriado para aplicações concorrentes.

Outros métodos que seguem o mesmo padrão

A biblioteca base do .NET possui dezenas de métodos iniciados por Try. Alguns exemplos bastante utilizados são:

- DateTime.TryParse(...)
- DateOnly.TryParse(...)
- TimeOnly.TryParse(...)
- decimal.TryParse(...)
- Guid.TryParse(...)
- Enum.TryParse(...)
- IPAddress.TryParse(...)
- Version.TryParse(...)
- Uri.TryCreate(...)
- Monitor.TryEnter(...)
- ConcurrentDictionary.TryRemove(...)
- ConcurrentDictionary.TryUpdate(...)
- ConcurrentBag.TryTake(...)
- ConcurrentStack.TryPop(...)
- ConcurrentStack.TryPeek(...)

Todos seguem exatamente a mesma filosofia: tentar executar a operação, retornar sucesso ou falha, e evitar exceções para condições previstas.

O padrão Try e o desempenho

Existe uma ideia equivocada de que um bloco try/catch torna o código lento. Na prática, entrar em um bloco try possui um custo muito pequeno nas implementações modernas do runtime do .NET.

O verdadeiro custo ocorre quando uma exceção é realmente lançada, porque o runtime precisa:

1. criar o objeto da exceção;
2. capturar a pilha de chamadas (stack trace);
3. iniciar o processo de propagação da exceção;
4. localizar um manipulador compatível.

Por esse motivo, exceções não devem ser utilizadas para controlar o fluxo normal da aplicação. Sempre que uma falha fizer parte do comportamento esperado, o padrão Try* costuma ser a melhor escolha.

Quando utilizar um método Try?

Utilize métodos Try* quando:
- a operação pode falhar naturalmente;
- a falha faz parte do fluxo esperado;
- deseja evitar exceções desnecessárias;
- pretende escrever código mais eficiente e expressivo.

Exemplos práticos incluem: validar entrada do usuário, procurar valores em coleções, converter dados, consumir filas concorrentes e manipular estruturas compartilhadas entre múltiplas threads.

Quando não utilizar o padrão Try?

Nem toda operação deve possuir uma versão Try. Considere uma aplicação bancária:
 
conta.Sacar(1000);

Se o saldo for insuficiente, isso representa uma violação de uma regra de negócio. Nesse cenário, normalmente faz mais sentido lançar uma exceção específica ou retornar um objeto que represente o resultado da operação, contendo informações detalhadas sobre o motivo da falha.

Outro exemplo seria tentar abrir um arquivo obrigatório da aplicação. Se o arquivo não existir, provavelmente existe um problema que merece interromper a execução. Portanto, o padrão Try não substitui o tratamento de exceções. Ele apenas evita exceções quando a possibilidade de falha faz parte do comportamento normal da operação.

Criando seu próprio método Try

Nada impede que você utilize essa convenção em suas próprias classes.

Considere o seguinte exemplo:
 
public static class ValidadorCpf
{
    public static bool TryValidar(string cpf,out string cpfFormatado)
    {
        cpfFormatado = string.Empty;

        cpf = cpf.Replace(".", "")
                 .Replace("-", "");

        if (cpf.Length != 11)
            return false;

        cpfFormatado =
            Convert.ToUInt64(cpf)
                   .ToString(@"000\.000\.000\-00");

        return true;
    }
}

Utilização:
 
if (ValidadorCpf.TryValidar(cpf, out string cpfFormatado))
{
    Console.WriteLine(cpfFormatado);
}
else
{
    Console.WriteLine("CPF inválido.");
}

Perceba como a API comunica claramente ao consumidor que a operação pode falhar sem necessidade de lançar exceções.

Boas práticas

Ao utilizar ou implementar métodos Try*, procure seguir algumas recomendações:

- utilize o prefixo Try apenas quando a operação puder falhar naturally;
- retorne bool;
- utilize um parâmetro out para disponibilizar o resultado;
- nunca utilize exceções para controlar o fluxo normal da aplicação;
- lance exceções apenas quando ocorrer uma condição realmente excepcional;
- sempre trate o retorno booleano antes de utilizar o valor obtido.

Conclusão

O padrão Try* é uma das convenções mais importantes da plataforma .NET. Ele torna as APIs mais previsíveis, melhora a legibilidade do código e evita o uso inadequado de exceções em situações consideradas normais.

Métodos como TryParse, TryGetValue, TryAdd e TryDequeue demonstram como uma API pode comunicar claramente que determinada operação pode não ser concluída, permitindo que o desenvolvedor trate esse cenário de forma simples e eficiente.

Ao compreender essa filosofia, você passa a escrever código mais alinhado com as diretrizes de projeto da plataforma .NET, produzindo aplicações mais robustas, performáticas e fáceis de manter.

E estamos conversados..

"Porque não me envergonho do evangelho de Cristo, pois é o poder de Deus para salvação de todo aquele que crê; primeiro do judeu, e também do grego.
"Romanos 1:16

Referências:


José Carlos Macoratti