Реализация интерфейса IDisposable на c#
Когда-то давно я разобрался в и так несложном интерфейсе IDisposable и сделал "правильный" пример реализации, по сути, просто откомментировав код с MSDN, для лучшего понимания, зачем и почему нужно делать именно так:
class DisposableExample: IDisposable {
#region Абсолютный минимум кода, который должен быть написан в правильной реализации IDisposable
///
/// Поле, где будет true, если объект уже пытались уничтожить.
///
private bool isDisposed = false;
///
/// Этот метод реализует интерфейс IDisposable.
/// Он либо вызывается программистом явно, либо неявно - из блока using()
/// Microsoft нам говорит, что этот метод не надо делать виртуальным, но не говорит, почему.
/// Скорее всего, просто чтоб не нарушился порядок вызова Dispose()/GC.SuppressFinalize().
///
public void Dispose() {
//Вызовем оверлоад метода и скажем, что его вызвали явно:
Dispose(true);
//Отменим вызов деструктора для этого объекта - всё, что нужно, мы уже за собой убрали.
//Это не критично (поскольку у нас есть защита от повторного вызова метода Dispose(bool) - флаг isDisposed),
//но поможет нам оптимизировать производительность.
GC.SuppressFinalize(this);
}
///
/// Это деструктор, он же finalizer.
/// Его вызывает Garbage Collector, когда фреймворк соизволит решить, что пора бы убраться.
///
~DisposableExample() {
//Вызываем метод очистки, указывая, что он вызван из деструктора.
//Если unmanaged ресурсов у нас нет, то тут "как бы" ничего нового не случится
//и особого сакрального смысла в этом вызове нет, но лучше перебдеть, чем недобдеть.
//Сюда мы придём только в том случае, если программист забыл уничтожить объект и не вызвал Dispose()
Dispose(false);
//Ничего больше в деструкторе быть не должно - весь код очистки ресурсов должен переехать в Dispose(bool)
}
///
/// Очистка ресурсов.
/// Чтобы не нарушить стройность логики в наследниках, этот метод не виртуальный,
/// виртуальными сделаны два метода DisposeManaged() и DisposeUnmanaged(), которые вызываются внутри.
///
///
/// True, если метод Dispose() вызывается явно, либо из блока using()
/// False, когда метод Dispose() вызывается из деструктора (неявно, при сборке мусора). Нельзя обращаться к Managed-ресурсам, о них позаботится Garbage Collector
/// В примере из MSDN параметр назван "disposing", что совершенно не проясняет его предназначение.
///
protected void Dispose(bool isCalledExplicitly) {
//Проверяем, что метод пока не вызывался:
if(!isDisposed) {
try {
if(isCalledExplicitly) {
//Dispose() был вызван явно программистом,
//он хотел освободить память, поскольку этот объект уже не понадобится.
//Garbage Collector до нашего объекта ещё не добрался,
//поэтому мы сами освобождаем managed-ресурсы.
DisposeManaged();
}
//Освобождаем unmanaged-ресурсы, о них Garbage Collector-у никто не даст знать,
//поэтому кроме нас их никто не освободит
DisposeUnmanaged();
} finally {
//Избегаем повторного вызова этого метода:
isDisposed = true;
}
}
}
///
/// Освобождение внутренних managed-объектов.
/// Может быть перекрыт в наследниках с вызовом base.DisposeManaged()
/// Если делается базовый класс, без наполнения метода функциональностью,
/// то метод должен быть не protected, а abstract.
///
protected virtual void DisposeManaged() {
//Здесь надо уничтожить все managed-ресурсы
//Все внутренние свойства, реализующие IDisposable, обрабатываются в этом методе:
//this.DisposableProperty.Dispose();
}
///
/// Освобождение внутренних unmanaged-ресурсов
/// Может быть перекрыт в наследниках с вызовом base.DisposeUnmanaged()
/// Если делается базовый класс, без наполнения метода функциональностью,
/// то метод должен быть не protected, а abstract.
///
protected virtual void DisposeUnmanaged() {
//Здесь очищаются ресурсы, о которых .Net Framework не знает,
//например, открытые file handles:
//CloseHandle(handle);
}
#endregion
#region Дополнительные свойства и методы, которые могут пригодиться
///
/// Вернёт true, если объект уже уничтожен.
/// Говорит о том, что этим объектом уже пользоваться не нужно.
///
protected bool IsDisposed { get { return isDisposed; } }
///
/// Метод выбросит эксепшн, если у объекта был вызван метод Dispose().
/// CheckDisposed можно вставлять первой строкой в другие методы,
/// которым важна целостность объекта.
///
protected void CheckDisposed() {
if(IsDisposed) throw new ObjectDisposedException(null);
}
#endregion
}
