Казалось бы, данный шаблон не просто прост, а очень прост, подробно разобран не в одной известной книге.
Тем не менее, до сих пор даже в рамках одного проекта он зачастую может быть реализован по-разному, создавая зоопарк из велосипедов, костылей и утечек.
Хочу поделиться своим способом реализации, который основан на минимизации изобретения велосипедов, максимальном уменьшении количества кода и увеличении его выразительности и прозрачности.
Предусловия
Никакого смешения управляемых и неуправляемых ресурсов
Я никогда не реализую сам и не советую коллегам использовать владение управляемыми и неуправляемыми ресурсами в одном классе.
Таким образом, один класс может:
- Не владеть ресурсами вообще
- Владеть одним неуправляемым ресурсом, то есть просто конвертировать его в управляемый
- Владеть одним или многими неуправляемыми ресурсами
Наследование реализаций нежелательно
Я не использую наследование от классов без крайней необходимости, предлагаемая реализация предполагает в качестве объекта-владельца ресурсов экземпляр запечатанного класса.
Это не значит, что ее нельзя модифицировать для поддержки наследования.
Обертки для неуправляемых ресурсов реализуется с помощью 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/
Добавить комментарий