Desafio C#

Dispose pattern completo com recurso não gerenciado

Enunciado

Implemente uma classe UnmanagedResourceWrapper que encapsula um recurso não gerenciado simulado (um IntPtr alocado com Marshal.AllocHGlobal). A classe deve seguir o Dispose pattern completo: implementar IDisposable, ter um finalizador, e um método protected virtual void Dispose(bool disposing). O método Dispose() público deve chamar Dispose(true) e GC.SuppressFinalize(this). No Main, crie uma instância dentro de um bloco using, use o recurso e, após o bloco, exiba "Recurso não gerenciado liberado.".

Requisitos

  • A classe deve alocar memória não gerenciada com Marshal.AllocHGlobal no construtor.
  • O método Dispose(bool) deve liberar a memória com Marshal.FreeHGlobal quando disposing for true ou false (pois é não gerenciado).
  • O finalizador deve chamar Dispose(false).
  • O Dispose() público deve chamar Dispose(true) e GC.SuppressFinalize(this).
  • A saída do programa deve ser Recurso não gerenciado liberado.

Código inicial

using System;
using System.Runtime.InteropServices;

public class UnmanagedResourceWrapper : IDisposable
{
    // Seu código aqui
}

class Program
{
    static void Main()
    {
        // Seu código aqui
    }
}

Saída esperada

Recurso não gerenciado liberado.
Ver dica

No Dispose(bool), libere a memória não gerenciada independentemente de disposing, pois ela não é gerenciada pelo GC. Use Marshal.FreeHGlobal(ptr).

Mostrar solução
using System;
using System.Runtime.InteropServices;

public class UnmanagedResourceWrapper : IDisposable
{
    private IntPtr _buffer;
    private bool _disposed = false;

    public UnmanagedResourceWrapper(int size)
    {
        // Aloca memória não gerenciada
        _buffer = Marshal.AllocHGlobal(size);
    }

    public void UseResource()
    {
        if (_disposed) throw new ObjectDisposedException(nameof(UnmanagedResourceWrapper));
        // Simula uso do recurso
        Console.WriteLine("Usando recurso não gerenciado...");
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;

        // Libera recursos não gerenciados (sempre)
        if (_buffer != IntPtr.Zero)
        {
            Marshal.FreeHGlobal(_buffer);
            _buffer = IntPtr.Zero;
        }

        // Se disposing for true, libere também recursos gerenciados (se houver)
        if (disposing)
        {
            // Nenhum recurso gerenciado adicional neste exemplo
        }

        _disposed = true;
    }

    ~UnmanagedResourceWrapper()
    {
        Dispose(false);
    }
}

class Program
{
    static void Main()
    {
        using (var wrapper = new UnmanagedResourceWrapper(1024))
        {
            wrapper.UseResource();
        } // Dispose é chamado aqui, liberando a memória não gerenciada
        Console.WriteLine("Recurso não gerenciado liberado.");
    }
}

Passo a passo

  1. A classe UnmanagedResourceWrapper declara um IntPtr para armazenar o ponteiro para a memória não gerenciada e um flag _disposed.
  2. O construtor aloca memória com Marshal.AllocHGlobal(size).
  3. O método UseResource verifica se o objeto já foi descartado e simula o uso do recurso.
  4. O método Dispose() público chama Dispose(true) e GC.SuppressFinalize(this) para evitar a finalização.
  5. O método Dispose(bool disposing) libera a memória não gerenciada com Marshal.FreeHGlobal independentemente de disposing, pois o GC não gerencia essa memória. Se disposing for true, também liberaria recursos gerenciados (não há neste exemplo).
  6. O finalizador chama Dispose(false) como fallback caso o usuário esqueça de chamar Dispose().
  7. No Main, usamos um bloco using para garantir a liberação imediata e exibimos a mensagem final.

Por que funciona

O Dispose pattern completo garante que recursos não gerenciados sejam liberados tanto no descarte explícito quanto na finalização. O GC.SuppressFinalize evita que o finalizador seja chamado após o Dispose já ter liberado a memória. O bloco using assegura que Dispose seja chamado mesmo com exceções.

Erros comuns

  • Não liberar memória não gerenciada no Dispose(bool) quando disposing é false: isso causa vazamento se o finalizador for chamado. A memória não gerenciada deve ser liberada em ambos os casos.
  • Esquecer de chamar GC.SuppressFinalize: o finalizador será executado desnecessariamente, impactando a performance.
  • Não verificar _disposed: chamar Dispose duas vezes pode causar FreeHGlobal duplo, corrompendo a memória.
  • Finalizador sem try/catch: exceções no finalizador podem derrubar o processo. Evite lançar exceções no finalizador.

Outra forma de resolver

Usar SafeHandle para encapsular o recurso não gerenciado. SafeHandle já implementa o padrão de forma robusta e é a recomendação moderna da Microsoft. Exemplo:

public class SafeBufferWrapper : SafeHandle
{
    public SafeBufferWrapper(int size) : base(IntPtr.Zero, true)
    {
        SetHandle(Marshal.AllocHGlobal(size));
    }

    public override bool IsInvalid => handle == IntPtr.Zero;

    protected override bool ReleaseHandle()
    {
        Marshal.FreeHGlobal(handle);
        return true;
    }
}

Isso simplifica o código e evita erros comuns.

Saída esperada

Usando recurso não gerenciado...
Recurso não gerenciado liberado.