Минимальная идентифицируемая имплимация только для управляемых ресурсов
Существует много информации о "стандартном полном" IDisposable
реализация для избавления от неуправляемых ресурсов - но в действительности этот случай (очень) редок (большинство ресурсов уже упакованы управляемыми классами). Этот вопрос фокусируется на минимальной реализации IDisposable для гораздо более распространенного случая "только для управляемых ресурсов".
1: является минимальной реализацией IDisposable
в приведенном ниже коде правильно, есть проблемы?
2: есть ли причина, чтобы добавить полный стандарт IDisposable
реализация (Dispose()
, Dispose(bool)
, Finalizer
и т. д.) по поводу минимальной имплиментации?
3: это нормально / мудро в этом минимальном случае, чтобы сделать Dispose
виртуальный (так как мы не предоставляем Dispose(bool)
)?
4: Если эта минимальная реализация заменена полной стандартной реализацией, которая включает (бесполезный, в данном случае) финализатор - изменится ли это, как GC обрабатывает объект? Есть ли минусы?
5: пример включает в себя Timer
и обработчики событий, так как эти случаи особенно важны, чтобы их не пропустить, так как их не удастся уничтожить, чтобы объекты оставались живыthis
в случае Timer
, eventSource
в случае обработчика события), пока GC не приступит к их утилизации в свое время. Есть ли другие примеры, подобные этим?
class A : IDisposable {
private Timer timer;
public A(MyEventSource eventSource) {
eventSource += Handler
}
private void Handler(object source, EventArgs args) { ... }
public virtual void Dispose() {
timer.Dispose();
if (eventSource != null)
eventSource -= Handler;
}
}
class B : A, IDisposable {
private TcpClient tpcClient;
public override void Dispose() {
(tcpClient as IDispose).Dispose();
base.Dispose();
}
}
рефов:
MSDN
SO: Когда мне нужно управлять управляемыми ресурсами
SO: Как избавиться от управляемого ресурса в методе Dispose() в C#
SO: Dispose() для очистки управляемых ресурсов
3 ответа
Реализация правильная, проблем нет при условии, что ни один производный класс напрямую не владеет неуправляемым ресурсом.
Одной из веских причин для реализации полного шаблона является "принцип наименьшего удивления". Поскольку в MSDN нет авторитетного документа, описывающего этот более простой шаблон, у разработчиков технического обслуживания могут возникать сомнения - даже у вас возникла необходимость спросить Stackru:)
Да, это нормально для Dispose, чтобы быть виртуальным в этом случае.
Затраты на ненужный финализатор незначительны, если был вызван метод Dispose, и он правильно реализован (т.е. вызывает GC.SuppressFinalize).
Подавляющее большинство IDisposable
классы вне самой.NET Framework IDisposable
потому что им удалось IDisposable
Ресурсы. Они редко могут напрямую удерживать неуправляемый ресурс - это происходит только при использовании P/Invoke для доступа к неуправляемым ресурсам, которые не предоставляются.NET Framework.
Поэтому, вероятно, есть хороший аргумент для продвижения этого более простого шаблона:
В тех редких случаях, когда используются неуправляемые ресурсы, они должны быть завернуты в запечатанные
IDisposable
Класс-обертка, который реализует финализатор (например, SafeHandle). Поскольку этот класс запечатан, этот класс не нуждается в полном шаблоне IDisposable.Во всех других случаях, подавляющее большинство, ваш более простой шаблон может быть использован.
Но до тех пор, пока Microsoft или какой-либо другой авторитетный источник не будет активно продвигать его, я буду продолжать использовать полный IDisposable
шаблон.
Другим вариантом является рефакторинг вашего кода, чтобы избежать наследования и сделать ваш IDisposable
занятия запечатаны. Тогда более простой шаблон легко оправдать, так как неловкие движения для поддержки возможного наследования больше не нужны. Лично я использую этот подход большую часть времени; в том редком случае, когда я хочу сделать незапечатанный класс одноразовым, я просто следую "стандартному" шаблону. Хорошая особенность культивирования этого подхода заключается в том, что он стремится подтолкнуть вас к компоновке, а не к наследованию, что обычно облегчает поддержку и тестирование кода.
Мой рекомендовал Dispose
шаблон для не виртуальных Dispose
реализация цепочки к виртуальной void Dispose(bool)
желательно после чего-то вроде:
int _disposed;
public bool Disposed { return _disposed != 0; }
void Dispose()
{
if (System.Threading.Interlocked.Exchange(ref _disposed, 1) != 0)
Dispose(true);
GC.SuppressFinalize(this); // In case our object holds references to *managed* resources
}
Использование этого подхода обеспечит Dispose(bool)
вызывается только один раз, даже если несколько потоков пытаются вызвать его одновременно. Хотя такие одновременные попытки утилизации редки (*), от них дешево обходиться; если базовый класс не делает что-то подобное, каждый производный класс должен иметь свою собственную избыточную защитную логику двойного расположения и, вероятно, также избыточный флаг.
(*) Некоторые классы связи, которые в основном однопоточные и используют блокирующий ввод / вывод, позволяют Dispose
вызываться из любого контекста потока для отмены операции ввода-вывода, которая блокирует свой собственный поток [очевидно, Dispose
не может быть вызван в этом потоке, так как этот поток не может ничего делать, пока он заблокирован]. Для таких объектов или объектов, которые их инкапсулируют, вполне возможно - и это не лишено смысла - попытаться создать внешний поток Dispose
их как средство прерывания их текущей операции в тот момент, когда они собирались быть уничтоженными их основным потоком. одновременный Dispose
вызовы, вероятно, будут редкими, но их возможность не указывает на какую-либо "проблему дизайна" при условии, что Dispose
Код может действовать на один вызов и игнорировать другой.