Коллекция вопросов ❓ на собеседовании в C# 🍧

Обзор вопросов по языка програмиированию C# и технологии NET


Project maintained by Dvurechensky-Docs Hosted on GitHub Pages — Theme by mattgraham

♻️ Что такое `IDisposable` и зачем он нужен?

Typing SVG

Static Badge

✨ Оглавление

⬆ Вернуться к главной

🔹 Краткое описание

Коротко: IDisposable — это интерфейс с одним методом Dispose(). Он нужен для детерминированного освобождения ресурсов (обычно: unmanaged-ресурсов и дорогих/внешних managed-ресурсов) в момент, когда объект больше не нужен. Это альтернатива ожиданию, пока сборщик мусора освободит объект — Dispose() позволяет вам завершить освобождение прямо сейчас.

Ниже — развёрнутое объяснение, примеры и лучшие практики.


🔹 Что он делает и почему нужен


🔹 Детали: сборщик мусора vs Dispose (финализаторы)


🔹 Как using связан с IDisposable и как он работает


🔹 Когда реализовывать IDisposable

Реализуйте IDisposable, если:

  1. Ваш класс владеет unmanaged-ресурсами напрямую (IntPtr, файловые дескрипторы и т.п.).
  2. Ваш класс содержит поля, которые реализуют IDisposable (например, Stream, SqlConnection, Timer) — вы должны освободить их.
  3. Нужно детерминированно завершать/освобождать что-то (отписываться от событий, останавливать таймеры, освобождать буферы и т.д.).

Если ваш класс не владеет ни unmanaged-ресурсами, ни IDisposable полями — как правило, IDisposable реализовывать не надо.


🔹 Правильный шаблон реализации (pattern) — для наследуемых и для запечатанных классов

1) Шаблон для НЕ-запечатанного (unsealed) класса — с поддержкой наследования и финализатором:

public class BaseResource : IDisposable
{
    private bool _disposed;

    // Пример managed-поля и unmanaged-поля (SafeHandle предпочтительнее IntPtr)
    private IDisposable? _managedResource;
    private SafeHandle? _safeHandle;

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this); // предотвращаем финализацию — ресурс уже освобождён
    }

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

        if (disposing)
        {
            // освобождаем управляемые ресурсы
            _managedResource?.Dispose();
        }

        // освобождаем неуправляемые ресурсы
        _safeHandle?.Dispose();

        _disposed = true;
    }

    ~BaseResource()
    {
        Dispose(false); // вызов при сборке мусора, если Dispose не был вызван
    }
}

Ключевые моменты:

2) Для sealed класса — проще (без виртуалов и финализатора, если нет unmanaged):

public sealed class SealedResource : IDisposable
{
    private bool _disposed;
    private readonly Stream _stream;

    public void Dispose()
    {
        if (_disposed) return;
        _stream?.Dispose();
        _disposed = true;
    }
}

Если sealed класс владеет unmanaged-ресурсами — можно всё равно использовать SafeHandle и финализатор не нужен, если SafeHandle используется корректно.


🔹 SafeHandle vs IntPtr


🔹 IAsyncDisposable и асинхронный Dispose


🔹 Структуры (struct) и IDisposable


🔹 Особые случаи и подводные камни


🔹 Примеры (коротко)

using → try/finally:

using (var r = new Resource()) // компилятор разворачивает
{
    // ...
}
// эквивалент:
var r = new Resource();
try { /* ... */ }
finally { if (r != null) ((IDisposable)r).Dispose(); }

IAsyncDisposable:

public class MyAsyncResource : IAsyncDisposable
{
    public async ValueTask DisposeAsync()
    {
        // асинхронная очистка
        await SomeAsyncCleanup();
        GC.SuppressFinalize(this);
    }
}

Проверка использований после Dispose:

private void ThrowIfDisposed()
{
    if (_disposed) throw new ObjectDisposedException(nameof(MyClass));
}

public void SomeMethod()
{
    ThrowIfDisposed();
    // ...
}

Потокобезопасное Dispose (Interlocked):

private int _disposed;
public void Dispose()
{
    if (Interlocked.Exchange(ref _disposed, 1) == 1) return;
    // освобождение
}

🔹 Когда НЕ реализовывать IDisposable


🔹 Быстрый чеклист — при реализации IDisposable сделайте:

  1. Освободить все IDisposable поля в Dispose().
  2. Если есть unmanaged-ресурсы — используйте SafeHandle или реализуйте правильный финализатор + Dispose(bool).
  3. Сделать Dispose() идемпотентным (повторные вызовы безопасны).
  4. Вызвать GC.SuppressFinalize(this) если у вас есть финализатор.
  5. Не вызывать виртуалы из финализатора; не бросать исключения в финализаторе.
  6. Убедиться, что методы после Dispose() корректно реагируют (например, кидают ObjectDisposedException).
  7. По возможности предпочесть using/using var для потребителей.

🔹 Короткие ответы на частые вопросы


⬆ Вернуться к главной

✨Dvurechensky✨