1. Como liberar recursos do jeito certo no C#?
Imagina o sistema operacional como um bibliotecário bem rígido. Você pega um livro (abre um arquivo/stream), lê, e depois... esquece de devolver! O bibliotecário fica bravo: "Como assim — o livro ainda tá com você?!" É igualzinho com streams. Um stream aberto ocupa recursos: descritor de arquivo, um pedaço de memória, e ainda bloqueia o arquivo pra outros apps.
Se você não fechar o stream, pode dar de "algo não funciona" até "quebrou tudo, ninguém consegue gravar nesse arquivo". E se o programa tiver vários streams abertos — o sistema pode começar a "vazar" recursos e simplesmente parar de funcionar.
Qual o perigo?
- Arquivo não fecha, comandos não vão pro disco (tipo, na gravação — os dados podem ficar no buffer).
- Arquivo fica bloqueado pra outros processos — colegas e outros programas ficam pistola.
- Limite de descritores: no Windows/Linux, processos têm limite de arquivos/streams abertos.
Interface IDisposable
Qualquer classe que mexe com recursos não gerenciados (streams, arquivos, banco de dados, sockets), tem que implementar a interface IDisposable.
public interface IDisposable
{
void Dispose();
}
Dentro do método Dispose() normalmente rola a liberação de todos os recursos: o arquivo finalmente fecha, conexões caem, memória é liberada.
Streams são objetos que seguram vários recursos importantes: arquivos, conexões, memória. Se não fechar, o arquivo pode ficar travado (teu Word vai falar "arquivo aberto por outro aplicativo!"), e o sistema — sem memória. Por isso é super importante liberar o stream depois de usar.
No .NET, streams implementam a interface IDisposable. Isso quer dizer: tem que fechar eles chamando Dispose() (ou só colocar num bloco using).
2. Jeitos de fechar um stream: do perigoso ao seguro
Opção 1. Fechar "na mão": não faça isso!
Esse é o jeito antigo. E mesmo que eu tenha mostrado nos exemplos antes, hoje em dia quase ninguém faz assim :P
var stream = new FileStream("file.txt", FileMode.Open);
// Trabalha com o stream
stream.Close(); // ou stream.Dispose()
Problema: se rolar um erro/exception entre abrir e fechar, o arquivo fica aberto e travado. É tipo sair correndo da biblioteca com o livro porque sentiu cheiro de pizza...
Opção 2. Usando try...finally
FileStream stream = null;
try
{
stream = new FileStream("file.txt", FileMode.Open);
// Trabalha com o stream
}
finally
{
if (stream != null)
stream.Dispose();
}
Esse jeito é seguro: finally sempre vai rodar, mesmo se der erro. Mas, fala sério, dá preguiça de escrever assim.
Opção 3. Bonito e seguro: operador using
Sintaxe clássica (using ( ... ) { ... })
using (var stream = new FileStream("file.txt", FileMode.Open))
{
// Trabalha com o stream
}
// Aqui stream.Dispose() é chamado automaticamente!
A ideia é: tudo que tá dentro do bloco using mexe com o stream, e quando o bloco acaba — o arquivo fecha mesmo se der ruim (tipo, exception).
Opção 4. Sintaxe moderna
Sintaxe moderna (using var)
using var stream = new FileStream("file.txt", FileMode.Open);
// Trabalha com o stream
// ... Dispose é chamado automaticamente quando a variável sai do escopo
Top! Não precisa criar bloco extra nem chaves.
Como funciona "por baixo dos panos"?
O operador using o compilador transforma naquele try...finally, só que pra você. Brincadeira: "using escreve código limpo por você — será que logo ele vai tomar café e ficar no Stack Overflow?".
Diferença entre o using clássico e o moderno
| Bloco using clássico | using var (declaração) | |
|---|---|---|
| Visual | |
|
| Escopo | Dentro das chaves do bloco | Até o fim do bloco atual (método, loop, etc.) |
| Enxuto | Um pouco mais verboso | Bem mais curto, menos indentação |
| Desde | C# 1.0 | C# 8.0 ou mais novo |
3. O que são using-declarações?
Faz uns 5 anos que apareceu um jeito novo e enxuto de trabalhar com objetos IDisposable.
using-declaração — é quando você, ao invés de bloco, declara a variável com a palavra using, e ela vai ser liberada automaticamente no fim do bloco atual (tipo, método ou loop), e não no fim das chaves de um bloco extra.
using var stream = new FileStream("file.txt", FileMode.Open);
// Trabalha com o stream
Console.WriteLine(stream.Length);
// Aqui o arquivo ainda tá aberto!
// ... fim do método
// stream.Dispose() é chamado aqui automaticamente
Diferente do using clássico:
- Não precisa de chaves, não cria bloco de código aninhado.
- A variável fica disponível até o fim do bloco onde foi declarada (normalmente — método, às vezes — loop, classe, se declarar no nível da classe).
- O recurso só é liberado quando o bloco termina.
Por que isso é massa?
- Menos níveis de indentação — o código ficou bem mais curto e legível.
- Mais fácil lidar com vários recursos — declara vários using seguidos, e tudo é liberado quando o método termina.
- Menos chance de vacilar — não esquece a área onde tinha que chamar Dispose().
4. Comparando: clássico vs. moderno using
Bora ver a comparação no código.
Jeito clássico
using (var reader = new StreamReader("input.txt"))
{
using (var writer = new StreamWriter("output.txt"))
{
string line;
while ((line = reader.ReadLine()) != null)
{
writer.WriteLine(line.ToUpper());
}
}
} // Aqui os dois arquivos vão ser fechados
Jeito moderno (C# 8+)
using var reader = new StreamReader("input.txt");
using var writer = new StreamWriter("output.txt");
string line;
while ((line = reader.ReadLine()) != null)
{
writer.WriteLine(line.ToUpper());
}
// Os dois arquivos fecham aqui, na saída do método
Bem mais simples, né? Principalmente se tiver mais níveis de aninhamento — o jeito moderno facilita muito a vida.
5. Quando e onde Dispose() é chamado?
Aqui a galera erra muito: acha que o Dispose é chamado logo depois da linha de uso — mas não é assim!
Olha esse exemplo:
void MeuMetodo()
{
using var fileStream = new FileStream("data.bin", FileMode.Open);
// ... muito código, talvez até loops e chamadas aninhadas
// fileStream ainda tá aberto!
// Aqui ainda pode acessar fileStream
}
// Aqui, no } do método, fileStream.Dispose() é chamado
Ponto importante: Se declarar uma variável using dentro de um loop, o Dispose é chamado a cada iteração.
foreach (var path in filePaths)
{
using var reader = new StreamReader(path);
// trabalha com reader
} // reader.Dispose() é chamado a cada iteração (fecha o arquivo)
6. Erros ao migrar código antigo
Às vezes você migra código antigo ou copia exemplo com using clássico, mas a variável precisa "viver" mais do que o escopo das chaves. Aí o clássico não serve, mas o using-declaração é perfeito.
Mas tem detalhes. Tipo, se no loop você tem dois recursos, mas um deles precisa "viver" mais que o outro — declara eles na ordem certa:
using var recurso1 = ...;
for (int i = 0; i < 10; i++)
{
using var recurso2 = ...;
// recurso2 vive uma iteração
// recurso1 — a função toda
}
7. Prática
Vamos continuar melhorando nosso app de estudo — um mini simulador de pedido de café, que você já mexeu nos últimos dias. Agora ele vai salvar o histórico de pedidos num arquivo de texto e ler eles ao iniciar.
Passo 1: Salvando pedido no arquivo
using var writer = new StreamWriter("orders.txt", append: true);
writer.WriteLine("Café: Latte; Leite: Aveia; Tamanho: Grande");
// O segundo parâmetro do construtor StreamWriter append: true diz que queremos adicionar, não sobrescrever o arquivo.
Passo 2: Lendo o histórico de pedidos
using var reader = new StreamReader("orders.txt");
string? line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine($"Pedido: {line}");
}
E assim que o programa terminar esse método, os arquivos fecham automaticamente.
8. Melhores práticas com using-declarações
1. Sempre use using pra objetos que implementam IDisposable
No .NET, a maioria das classes pra arquivos, streams, recursos implementam essa interface. Isso é o sinal: libera usando using!
2. Fique ligado no escopo: não declare using-var onde a variável pode "atrapalhar"
Se a variável só serve pra umas linhas — usa ela só onde precisa, e não antes.
3. Não esqueça da ordem de liberação
Se declarar vários using seguidos — Dispose é chamado na ordem inversa:
using var first = new Resource("Primeiro");
using var second = new Resource("Segundo");
// ... trabalho
// Primeiro Dispose do second, depois do first
Isso às vezes importa, tipo se um recurso depende do outro (por exemplo, stream de escrita tem que liberar antes do arquivo).
4. Não use using-declaração fora de método
using-declarações são proibidas no nível da classe (tipo, pra fields). Só funcionam dentro de métodos, construtores, etc.
5. Combine com tratamento de erro
Mesmo com using nem todo exception é tranquilo — vale a pena colocar try-catch se quiser controlar erros de leitura/gravação.
GO TO FULL VERSION