Самая простая и надежная реализация шаблона проектирования Dispose на .NET

от автора


Казалось бы, данный шаблон не просто прост, а очень прост, подробно разобран не в одной известной книге.
Тем не менее, до сих пор даже в рамках одного проекта он зачастую может быть реализован по-разному, создавая зоопарк из велосипедов, костылей и утечек.
Хочу поделиться своим способом реализации, который основан на минимизации изобретения велосипедов, максимальном уменьшении количества кода и увеличении его выразительности и прозрачности.

Предусловия

Никакого смешения управляемых и неуправляемых ресурсов

Я никогда не реализую сам и не советую коллегам использовать владение управляемыми и неуправляемыми ресурсами в одном классе.
Таким образом, один класс может:

  • Не владеть ресурсами вообще
  • Владеть одним неуправляемым ресурсом, то есть просто конвертировать его в управляемый
  • Владеть одним или многими неуправляемыми ресурсами

Наследование реализаций нежелательно

Я не использую наследование от классов без крайней необходимости, предлагаемая реализация предполагает в качестве объекта-владельца ресурсов экземпляр запечатанного класса.
Это не значит, что ее нельзя модифицировать для поддержки наследования.

Обертки для неуправляемых ресурсов реализуется с помощью Janitor.Fody

Этот плагин к Fody — бесплатному инструменту модификации кода сборок после компиляции — позволит не выписывать вручную тысячу и одну деталь реализации, необходимой для корректного освобождения ресурсов.
Ваш код (пример из документации):

public class Sample : IDisposable {     IntPtr handle;      public Sample()     {         handle = new IntPtr();     }      public void Method()     {         //Some code     }      public void Dispose()     {         //must be empty     }      void DisposeUnmanaged()     {         CloseHandle(handle);         handle = IntPtr.Zero;     }      [DllImport("kernel32.dll", SetLastError=true)]     static extern bool CloseHandle(IntPtr hObject); } 

Результат постобработки:

public class Sample : IDisposable {     IntPtr handle;     volatile int disposeSignaled;     bool disposed;      public Sample()     {         handle = new IntPtr();     }      void DisposeUnmanaged()     {         CloseHandle(handle);         handle = IntPtr.Zero;     }      [DllImport("kernel32.dll", SetLastError = true)]     static extern Boolean CloseHandle(IntPtr handle);      public void Method()     {         ThrowIfDisposed();         //Some code     }      void ThrowIfDisposed()     {         if (disposed)         {             throw new ObjectDisposedException("TemplateClass");         }     }      public void Dispose()     {         if (Interlocked.Exchange(ref disposeSignaled, 1) != 0)         {             return;         }         DisposeUnmanaged();         GC.SuppressFinalize(this);         disposed = true;     }       ~Sample()     {         Dispose();     } } 

Теперь можно перейти к самому распространенному случаю.

Реализация шаблона проектирования Dispose для управляемых ресурсов

Подготовка

Для начала нам потребуется класс CompositeDisposable из библиотеки Reactive Extensions.
К нему необходимо дописать небольшой метод расширения:

public static void Add(this CompositeDisposable litetime, Action action) {     lifetime.Add(Disposable.Create(action)); } 

Добавление поля, отвечающего за очистку

private readonly CompositeDisposable lifetime = new CompositeDisposable(); 

Реализация метода Dispose

public void Dispose() {     lifetime.Dispose(); } 

Больше ничего и никогда в этот метод добавлять не нужно.

Очистка явно конструируемых ресурсов

Достаточно просто добавить простейший код прямо в место выделения ресурса.
Было:

myOwnedResourceField = new Resource(); 

Стало:

lifetime.Add(myOwnedResourceField = new Resource()); 

Отписка от событий

Было:

sender.Event += Handler; 

Стало:

sender.Event += Handler; lifetime.Add(() => sender.Event -= Handler); 

Отписка от IObservable

Было:

observable.Subscribe(Handler); 

Стало:

lifetime.Add(observable.Subscribe(Handler)); 

Выполнение произвольных действий при очистке

CreateAction(); lifetime.Add(() => DisposeAction()); 

Проверка состояния объекта

if (lifetime.IsDisposed) 

Выводы

Предлагаемый способ:

  • универсален: гарантированно покрываются любые управляемые ресурсы, даже такие как «при очистке выполните следующий код
  • выразителен: дополнительный код невелик по объему
  • привычен: используется обыкновенный класс из очень популярной библиотеки, который, вдобавок, при необходимости несложно написать и самостоятельно
  • прозрачен: код очистки каждого ресурса расположен вплотную к коду захвата, большинство потенциальных утечек будут сразу замечены при рецензировании
  • ухудшает производительность: добавляет „memory traffic“ за счет создания новых объектов
  • не влияет на безопасность использования уже „мертвого“ объекта: собственные ресурсы очистятся только однажды, но любые проверки с выбросом ObjectDisposedException надо делать вручную

Буду рад, если описанный способ пригодится читателям.

ссылка на оригинал статьи http://habrahabr.ru/post/270929/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *