Минимальная идентифицируемая имплимация только для управляемых ресурсов

Существует много информации о "стандартном полном" 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 ответа

Решение
  1. Реализация правильная, проблем нет при условии, что ни один производный класс напрямую не владеет неуправляемым ресурсом.

  2. Одной из веских причин для реализации полного шаблона является "принцип наименьшего удивления". Поскольку в MSDN нет авторитетного документа, описывающего этот более простой шаблон, у разработчиков технического обслуживания могут возникать сомнения - даже у вас возникла необходимость спросить Stackru:)

  3. Да, это нормально для Dispose, чтобы быть виртуальным в этом случае.

  4. Затраты на ненужный финализатор незначительны, если был вызван метод 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 Код может действовать на один вызов и игнорировать другой.

Другие вопросы по тегам