Паттерн IDisposable
Я не зря запостил сразу весь исходник класса BufferPool. В дальнейшем я собираюсь пройтись по тем маленьким решениям, которые неизбежно приходится принимать при проектировании и реализации классов и объяснить, почему я принял те решения, которые принял. Итак, паттерн IDisposable. Все знают что в .NET нет детерминированных деструкторов. Разработчики сделали все чтобы скрыть от нас тот момент, когда класс освобождается. Мы не можем вызвать деструктор сами. Взамен предлагают два подхода:
- IDisposable.Dispose() или Close()
- Функция Finalize() (имеет синтаксис деструктора в C#)
IDisposable реализуем если выполняется одно или оба условия:
- Данный класс содержит неуправляемые (unmanaged) ресурсы.
- Данный класс содержит члены, которые реализуют IDisposable
Finalize реализуем только в случае неуправляемых ресурсов. Внутренние классы освобождать вызовом Dispose() из Finalize() ненадо, даже запрещено(!), так как они уже могут быть освобожденны с помощью финализации. Собственно сам паттерн IDisposable выглядит так:
class Foo : IDisposable {Теперь посмотрим внимательно на класс BufferPool. Что мы видим? Роль логической переменной disposed играет роль поле mrEvent, если оно == null, то значит кто-то уже вызвал Dispose(). Хорошо. Так же мы видим 2 метода Dispose(), полностью соответствующих паттерну IDisposable. Но где метод Finalize (деструктор в С#)? Правильно, его нет. Потому что наш класс непосредственно неуправляемыми ресурсами не владеет. У нас есть только mrEvent, который сам владеет unmanaged ресурсами, но освобождать их его забота, а не наша!
bool disposed = false;
public Foo() {
}
~Foo() {
Dispose(false);
}
public void Dispose() {
Dispose(true);
}
// Метод Dispose должен быть написан так, чтобы допускать
// много вызовов (игнорировать повторные)
protected virtual void Dispose(bool disposing) {
// сделать общие действия для Dispose и для Finalize
if (disposing) {
// сделать то что должен делать Dispose
// .....
disposed = true;
// сказать GC, что Finalize для данного класса
// вызывать ненадо
GC.SuppressFinalize(this);
} else {
// сделать то, что должен делать Finalize.
// ВАЖНО: нельзя обращаться к элементам
// нашего класс со ссылочными типами
// (которые не наследуются от ValueType)
// .....
}
}
// При вызове метода, которые не может быть выполнен
// без уже освобожденных ресурсов следует бросать ObjectDisposedException
public void DoWork() {
if (disposed) throw new ObjectDisposedException("Foo");
}
}
Коментарі