Médio C#
Implementando `IDisposable` com recurso gerenciado
Enunciado
Crie uma classe Logger que implementa IDisposable. Ela deve ter um construtor que recebe um caminho de arquivo e abre um StreamWriter para escrita. O método Log(string message) escreve a mensagem no arquivo. O método Dispose() deve fechar o StreamWriter. No Main, use a classe dentro de um bloco using para escrever duas mensagens e depois exiba "Log finalizado.".
Requisitos
- A classe
Loggerdeve implementarIDisposable. - O método
Dispose()deve fechar oStreamWritere suprimir a finalização. - O programa deve usar
usingpara garantir o descarte. - A saída no console deve ser exatamente
Log finalizado.
Código inicial
using System;
using System.IO;
public class Logger : IDisposable
{
// Seu código aqui
}
class Program
{
static void Main()
{
// Seu código aqui
}
}
Saída esperada
Log finalizado.
Ver dica
No Dispose, chame _writer?.Dispose() e GC.SuppressFinalize(this). Não é necessário finalizador se não houver recursos não gerenciados diretamente.
Mostrar solução
using System;
using System.IO;
public class Logger : IDisposable
{
private StreamWriter _writer;
private bool _disposed = false;
public Logger(string path)
{
_writer = new StreamWriter(path);
}
public void Log(string message)
{
if (_disposed) throw new ObjectDisposedException(nameof(Logger));
_writer.WriteLine(message);
}
public void Dispose()
{
if (_disposed) return;
_writer?.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}
class Program
{
static void Main()
{
using (var logger = new Logger("app.log"))
{
logger.Log("Primeira mensagem");
logger.Log("Segunda mensagem");
} // Dispose é chamado aqui
Console.WriteLine("Log finalizado.");
}
}
Passo a passo
- A classe
LoggerimplementaIDisposablee mantém um campoStreamWritere um flag_disposed. - O construtor inicializa o
StreamWritercom o caminho fornecido. - O método
Logverifica se o objeto já foi descartado e, caso contrário, escreve a mensagem. - O método
Disposefecha oStreamWriter(se não for nulo), marca_disposedcomotruee chamaGC.SuppressFinalizepara evitar finalização desnecessária. - No
Main, usamos um blocousingpara criar oLogger, escrever duas mensagens e, ao sair do bloco, oDisposeé chamado automaticamente. - Por fim, exibimos
Log finalizado.no console.
Por que funciona
O bloco using garante que Dispose seja chamado mesmo se ocorrer uma exceção durante o logging. O StreamWriter interno também é descartado corretamente, liberando o arquivo. O GC.SuppressFinalize informa ao coletor de lixo que não é mais necessário chamar o finalizador, otimizando a coleta.
Erros comuns
- Não verificar
_disposednoDispose: chamarDisposeduas vezes pode causar exceções. Sempre verifique. - Esquecer
GC.SuppressFinalize: se houver finalizador, ele será chamado desnecessariamente, impactando performance. - Não descartar recursos internos: se
_writernão for descartado, o arquivo permanece aberto. - Lançar exceção no
Dispose: evite; se necessário, apenas registre.
Outra forma de resolver
Implementar o padrão completo com Dispose(bool) e finalizador, mesmo sem recursos não gerenciados diretamente. Isso é útil se a classe for herdada e puder conter recursos não gerenciados. Exemplo:
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
_writer?.Dispose();
}
_disposed = true;
}
~Logger() => Dispose(false);
Saída esperada
Log finalizado.