Приходилось ли вам работать с неуправляемыми ресурсами в C#?

«Приходилось ли вам работать с неуправляемыми ресурсами в C#?» — вопрос из категории Управление памятью, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, работа с неуправляемыми ресурсами требует особого внимания к управлению памятью. Вот ключевые подходы:

1. Паттерн Disposable:

public class FileProcessor : IDisposable
{
    private IntPtr _handle; // WinAPI handle
    private bool _disposed = false;

    public FileProcessor(string path)
    {
        _handle = NativeMethods.CreateFile(path, ...);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                // Освобождение управляемых ресурсов
            }

            // Освобождение неуправляемых ресурсов
            if (_handle != IntPtr.Zero)
            {
                NativeMethods.CloseHandle(_handle);
                _handle = IntPtr.Zero;
            }

            _disposed = true;
        }
    }

    ~FileProcessor() => Dispose(false);

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

// Использование
using (var processor = new FileProcessor("data.bin"))
{
    processor.Process();
} // Автоматический вызов Dispose()

2. SafeHandle для безопасной работы:

public sealed class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr hObject);

    public SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle) 
        : base(ownsHandle)
    {
        SetHandle(preexistingHandle);
    }

    protected override bool ReleaseHandle()
    {
        return CloseHandle(handle);
    }
}

3. Распространенные сценарии:

  • COM-объекты через Marshal.ReleaseComObject()
  • GDI+ ресурсы (Bitmap, Graphics)
  • Сетевые сокеты и дескрипторы файлов
  • Выделенная неуправляемая память через Marshal.AllocHGlobal()

4. Best practices:

  • Всегда реализовывать IDisposable для классов с неуправляемыми ресурсами
  • Использовать using для гарантированного освобождения
  • Добавлять финализатор только если ресурс критически важен
  • Проверять IntPtr.Zero перед освобождением