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.AllocHGlobalno construtor. - O método
Dispose(bool)deve liberar a memória comMarshal.FreeHGlobalquandodisposingfortrueoufalse(pois é não gerenciado). - O finalizador deve chamar
Dispose(false). - O
Dispose()público deve chamarDispose(true)eGC.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
- A classe
UnmanagedResourceWrapperdeclara umIntPtrpara armazenar o ponteiro para a memória não gerenciada e um flag_disposed. - O construtor aloca memória com
Marshal.AllocHGlobal(size). - O método
UseResourceverifica se o objeto já foi descartado e simula o uso do recurso. - O método
Dispose()público chamaDispose(true)eGC.SuppressFinalize(this)para evitar a finalização. - O método
Dispose(bool disposing)libera a memória não gerenciada comMarshal.FreeHGlobalindependentemente dedisposing, pois o GC não gerencia essa memória. Sedisposingfortrue, também liberaria recursos gerenciados (não há neste exemplo). - O finalizador chama
Dispose(false)como fallback caso o usuário esqueça de chamarDispose(). - No
Main, usamos um blocousingpara 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)quandodisposingé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: chamarDisposeduas vezes pode causarFreeHGlobalduplo, 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.