Uma falha precisa de uma decisão
Uma exceção comunica que uma operação não terminou pelo caminho normal. O tipo, o contexto e o contrato do chamador determinam a resposta. Num ficheiro opcional, a ausência pode autorizar uma configuração predefinida; falta de permissão não prova ausência e conteúdo inválido não deve ser aceite silenciosamente.
Delimita o try à operação cuja falha sabes interpretar. Se envolveres leitura, análise e ativação no mesmo catch genérico, podes atribuir ao ficheiro ausente um erro de programação ocorrido depois. Um catch específico pode converter uma falha esperada num resultado explícito; se apenas acrescentares informação de diagnóstico, usa throw; para continuar a propagação conservando o stack trace.
A mensagem textual é útil para diagnóstico, mas não substitui uma classificação pelo tipo e pelo contexto. Evita transformar qualquer falha num resultado aparentemente válido.
Handlers, filtros e saída do âmbito
Os catch são considerados pela ordem escrita; um filtro when acrescenta uma condição ao tipo. Se optional for false, catch (IOException) when (optional) não trata a exceção e a procura continua. Um handler seguinte pode tratar o mesmo tipo sem esse filtro.
Um finally serve para ações de saída durante o fluxo normal ou o desenrolamento habitual de uma exceção, incluindo return; não significa que a operação tenha sido aceite. Mantém o código de limpeza pequeno: se o próprio finally lançar outra exceção, essa falha pode substituir a que estava a propagar-se.
Também não uses a chamada de limpeza como prova de rollback: libertar um leitor ou uma ligação não desfaz automaticamente efeitos de negócio já produzidos. Se o processo for terminado abruptamente, não construas garantias de recuperação durável apenas com finally.
using define quem liberta e quando
O garbage collector gere memória de objetos; recursos com Dispose podem precisar de libertação antes da recolha. Um bloco using limita a utilização do recurso ao seu âmbito. Uma declaração using var reader = ...; liberta-o no fim do âmbito que contém a declaração, não no fim dessa linha.
Um return antecipado continua a passar pela libertação, desde que a execução percorra os mecanismos normais de saída. Duas aquisições aninhadas são libertadas de dentro para fora. Se OpenA tiver sucesso e OpenB lançar antes de devolver um recurso, o using exterior ainda liberta A; não existe um B entregue ao using para libertar.
A própria implementação de OpenB é responsável por recursos que tenha adquirido internamente antes de falhar. Não confundas a referência readonly declarada por using com um objeto imutável.
Recursos próprios e recursos recebidos
Uma função que abre um ficheiro e termina a leitura normalmente é responsável por fechar o leitor que criou. Quando recebe um Stream do chamador, a responsabilidade precisa de ser acordada. Por predefinição, libertar um StreamReader também fecha o stream que lhe foi fornecido.
A sobrecarga com leaveOpen: true permite libertar o leitor conservando esse stream aberto para o dono. Isto não promete conservar a posição inicial: a leitura e o buffering podem avançar o stream, e o chamador deve gerir a posição segundo o seu protocolo.
Também não basta retirar using para evitar fechar um recurso emprestado; isso deixa por cumprir a libertação do leitor. No desenho da API, declara se o recurso é transferido, emprestado ou criado internamente. Essa decisão deve ser a mesma em sucesso e em falha.
Ficheiros: observar não reserva o recurso
File.ReadAllText abre, lê e fecha o ficheiro, devolvendo uma string. Não transfere um handle aberto e a string não volta a ler o disco quando é consultada. Para conteúdos muito grandes, obter todo o texto de uma vez pode ultrapassar o orçamento de memória; escolhe uma estratégia compatível com os limites de entrada.
File.Exists é apenas uma observação e pode devolver false para causas diferentes de ausência, como certos erros de acesso. Mesmo depois de true, outro processo pode remover ou substituir o ficheiro antes da leitura. Trata o resultado da operação efetiva.
Define ainda a base dos caminhos relativos. Em Linux, Path.Combine("/srv/imports", "/tmp/item.txt") devolve o segundo caminho absoluto. Combinar strings não verifica que o destino permanece dentro de um diretório autorizado, nem substitui as regras de acesso da aplicação.
Não devolver trabalho dependente de um recurso fechado
Considera um método normal que declara using var reader = new StringReader("A\nB") e devolve new[] { 1, 2 }.Select(_ => reader.ReadLine()). O resultado é uma consulta diferida. Quando o método termina, using liberta reader; só depois o consumidor tenta enumerar e a primeira leitura encontra o recurso fechado.
Conservar a referência capturada não reverte Dispose. Para uma entrada pequena, podes concluir as leituras e materializar os valores antes de sair do âmbito. Outra API pode transferir explicitamente o recurso, mas precisa de um dono e de regras de fecho.
Não confundas este método normal com um método iterador que usa yield: esse desenho tem outro tempo de vida e deve ser analisado como tal. Ao rever uma função, pergunta se o valor devolvido já contém dados independentes ou se ainda necessita de I/O posterior.
Leitura guiada: aceitar um título antes de devolver
O exemplo abre um ficheiro de texto controlado pela aplicação e obtém a primeira linha antes de devolver. Assume um caminho confiável, texto UTF-8 válido e linhas com tamanho limitado pelo processo que produz o ficheiro. O método trata um ficheiro vazio ou uma primeira linha vazia ou só com espaços em branco como conteúdo inválido.
Um título " Estado diário " torna-se "Estado diário". O retorno é uma string já obtida, pelo que fechar reader não impede o consumidor de a usar. Falhas de acesso ou abertura propagam-se; este método não aplica configurações predefinidas.
O chamador pode tratar uma ausência autorizada num âmbito adequado. Ler uma linha não valida todas as linhas do ficheiro nem demonstra consistência perante escritores concorrentes. Nas perguntas, identifica o dono do recurso, o ponto de falha e a decisão permitida, sem precisar de executar o excerto.
using System;
using System.IO;
public static class Titles
{
public static string ReadTitle(string path)
{
using var reader = File.OpenText(path);
string? line = reader.ReadLine();
if (line is null || line.Trim().Length == 0)
throw new FormatException("A title is required.");
return line.Trim();
}
}Num ficheiro UTF-8 controlado com a primeira linha " Estado diário ", ReadTitle devolve "Estado diário" e fecha o leitor antes de o chamador receber o resultado.
Armadilhas comuns
Converter todas as exceções em defaults; usar throw ex; confundir using com rollback; fechar streams emprestados; tomar Exists por reserva; devolver consultas que usam leitores já fechados.
Tópicos relacionados: LINQ, trabalho diferido e materialização · Referências, nullability e texto · Tarefas assíncronas e cancelamento
Trata apenas as falhas que sabes interpretar e mantém o tempo de vida do recurso compatível com todo o trabalho que depende dele.
Referência: Exception-handling statements · C# 14 / .NET 10; no preview features