Имплементация интерфейса IDisposable

Интерфейс IDisposable служит для освобождения неуправляемых ресурсов (unmanaged resources). Сборщик мусора в .NET автоматически не освобождает неуправляемую память. Поэтому разработан шаблон освобождения неуправляемых ресурсов (Dispose Pattern), таких как файловые дескрипторы, указатели на блоки неуправляемой памяти, дескрипторы реестра и т.п. Для освобождения неуправляемых ресурсов объект должен реализовывать интерфейс IDisposable.

Интерфейс IDisposable требует имплементации метода без параметров Dispose(), а незапечатанные (non-sealed) классы дополнительно должны перегружать метод Dispose(bool). Чтобы обеспечить правильное освобождение ресурсов, метод Dispose должен быть идемпотентным (т.е. чтобы его можно было без вреда вызывать несколько раз). Все последующие после первого вызовы Dispose() ничего не должны делать.

Стандартная реализация публичного невиртуального метода Dispose():

1
2
3
4
5
public void Dispose()
{
Dispose(true); // Dispose of unmanaged resources
GC.SuppressFinalize(this); // Suppress finalization
}

Перегруженный метод Dispose(bool) выполняет фактическую очистку всех объектов, поэтому сборщику мусора больше не нужно вызывать финализатор (деструктор) объектов. Таким образом, вызов метода SuppressFinalize предотвращает запуск финализатора (деструктора) сборщиком мусора. Если класс не имеет финализатора (деструктора), вызов GC.SuppressFinalize не будет оказывать никакого влияния.

В перегруженном методе Dispose(bool) аргумент метода указывает исходит ли вызов из метода Dispose (тогда его значение true) или из финализатора (тогда его значение false).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;

if (disposing)
{
// TODO: dispose managed state (managed objects).
}

// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.

_disposed = true;
}

Метод немедленно возвращается, если очиска ресурсов уже имела место до этого. Отмечу, что блок выполняющий очистку неуправляемых ресурсов выполняется при любом значении аргумента disposing у метода. А вот блок освобождающий управляемые ресурсы запустится только в случае аргумента disposing равного true. Управляемые ресурсы могут состоять из объектов, реализующих IDisposable интерфейс (тогда у них просто надо каскадно вызвать Dispose() методы), либо из объектов, потребляющих большие объемы памяти или ограниченные ресурсы (тогда надо назначить ссылкам на них значение null, что освобождает их быстрее, чем если бы они были очищены в произвольный момент).

Если вызов метода происходит из финализатора (деструктора класса), то должен выполняться только код, освобождающий неуправляемые ресурсы, как показано ниже:

1
2
3
4
~A()
{
Dispose(false);
}

Разработчик отвечает за то, чтобы путь с аргументом false не взаимодействовал с управляемыми объектами, которые уже могли быть удалены. Это важно, потому что порядок в котором сборщик мусора удаляет управляемые объекты во время финализации недетерминирован.

Отмечу, что следует реализовать финализатор (деструктор) только в том случае, если у вас есть фактические неуправляемые ресурсы для удаления. Одна из основных причин реализовать финализатор (деструктор) состоит в том, что вы не может быть уверены инициализирован ли полностью экземпляр (например, в конструкторе может быть сгенерировано исключение). Однако если базовый класс может ссылаться только на управляемые объекты и реализовывать шаблон удаления, то в таком случае финализатор (деструктор) не нужен. Финализатор (деструктор класса) требуется только в том случае, если вы напрямую ссылаетесь на неуправляемые ресурсы. CLR обрабатывает финализируемые объекты иначе, чем нефинализируемые, даже если вызывается SuppressFinalize.

Когда класс реализует интерфейс IDisposable, это означает, что где-то есть неуправляемые ресурсы от которых следует избавиться, когда вы закончите использовать объект этого класса. Ресурсы инкапсулированы внутри класса и вам не нужно явно удалять их. Простой вызов Dispose() или обертывание класса в using(...) {} позволит избавиться от любых неуправляемых ресурсов автоматически.

Таким образом, вот общий шаблон для реализации Dispose Pattern:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using System;

class ClassWithFinalizer : IDisposable
{
// To detect redundant calls
private bool _disposedValue;

~ClassWithFinalizer() => Dispose(false);

// Public implementation of Dispose pattern callable by consumers.
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

// Protected implementation of Dispose pattern.
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects)
}

// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
_disposedValue = true;
}
}
}