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 Logger deve implementar IDisposable.
  • O método Dispose() deve fechar o StreamWriter e suprimir a finalização.
  • O programa deve usar using para 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

  1. A classe Logger implementa IDisposable e mantém um campo StreamWriter e um flag _disposed.
  2. O construtor inicializa o StreamWriter com o caminho fornecido.
  3. O método Log verifica se o objeto já foi descartado e, caso contrário, escreve a mensagem.
  4. O método Dispose fecha o StreamWriter (se não for nulo), marca _disposed como true e chama GC.SuppressFinalize para evitar finalização desnecessária.
  5. No Main, usamos um bloco using para criar o Logger, escrever duas mensagens e, ao sair do bloco, o Dispose é chamado automaticamente.
  6. 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 _disposed no Dispose: chamar Dispose duas 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 _writer nã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.