Как предотвратить распространение IDisposable на все ваши классы?
Начнем с этих простых классов...
Допустим, у меня есть простой набор классов, как это:
class Bus
{
Driver busDriver = new Driver();
}
class Driver
{
Shoe[] shoes = { new Shoe(), new Shoe() };
}
class Shoe
{
Shoelace lace = new Shoelace();
}
class Shoelace
{
bool tied = false;
}
A Bus
есть Driver
на Driver
два Shoe
s, каждый Shoe
есть Shoelace
. Все очень глупо.
добавить IDisposable объект для шнурка
позже я решаю, что некоторые операции на Shoelace
может быть многопоточным, поэтому я добавить EventWaitHandle
для потоков для связи с. Так что Shoelace
теперь выглядит это:
class Shoelace
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
// ... other stuff ..
}
реализовать IDisposable на шнурке
а теперь Microsoft FxCop буду жаловаться: "реализовать IDisposable на 'Shoelace', потому что он создает члены следующих типов IDisposable: 'EventWaitHandle'."
хорошо, я реализую IDisposable
on Shoelace
и мой аккуратный маленький класс становится этим ужасным беспорядком:
class Shoelace : IDisposable
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
private bool disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~Shoelace()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
if (waitHandle != null)
{
waitHandle.Close();
waitHandle = null;
}
}
// No unmanaged resources to release otherwise they'd go here.
}
disposed = true;
}
}
или (как указывают комментаторы) с Shoelace
сам не имеет неуправляемого ресурсы, я мог бы использовать более простую реализацию dispose без необходимости Dispose(bool)
и деструктор:
class Shoelace : IDisposable
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
public void Dispose()
{
if (waitHandle != null)
{
waitHandle.Close();
waitHandle = null;
}
GC.SuppressFinalize(this);
}
}
смотреть в ужасе, как IDisposable распространяется
правильно, что это исправлено. Но теперь FxCop будет жаловаться, что Shoe
создает Shoelace
, так что Shoe
должно быть IDisposable
тоже.
и Driver
создает Shoe
так Driver
должно быть IDisposable
.
И Bus
создает Driver
так Bus
должно быть IDisposable
и так далее.
вдруг мой небольшое изменение в Shoelace
вызывает у меня много работы, и мой босс задается вопросом, почему мне нужно в кассу Bus
изменить Shoelace
.
Вопрос
как предотвратить это распространение IDisposable
, но все же убедитесь, что ваши неуправляемые объекты правильно расположены?
8 ответов:
вы не можете действительно "предотвратить" IDisposable от распространения. Некоторые классы должны быть удалены, как
AutoResetEvent
, и самый эффективный способ сделать это вDispose()
метод, чтобы избежать финализаторы. Но этот метод должен быть вызван каким-то образом, так же, как и в вашем примере, классы, которые инкапсулируют или содержат IDisposable, должны их утилизировать, поэтому они также должны быть одноразовыми и т. д. Единственный способ избежать этого - это:
- избегайте использования IDisposable классов, где возможно, блокировка или ожидание событий в отдельных местах, хранение дорогостоящих ресурсов в одном месте и т. д.
- создавайте их только тогда, когда они вам нужны, и утилизируйте их сразу после (the
using
шаблон)В некоторых случаях IDisposable можно игнорировать, поскольку он поддерживает необязательный случай. Например, WaitHandle реализует IDisposable для поддержки именованного мьютекса. Если имя не используется, метод Dispose ничего не делает. MemoryStream-это еще один пример, он не использует систему ресурсы и его реализация Dispose также ничего не делает. Тщательное размышление о том, используется ли неуправляемый ресурс или нет, может быть обучающим. Так же можно изучить доступные источники для библиотек .net или использовать декомпилятор.
с точки зрения корректности вы не можете предотвратить распространение IDisposable через отношение объекта, если родительский объект создает и по существу владеет дочерним объектом, который теперь должен быть одноразовым. FxCop является правильным в этой ситуации, и родитель должен быть IDisposable.
Что вы можете сделать, это избежать добавления IDisposable к классу leaf в иерархии объектов. Это не всегда простая задача, но это интересное упражнение. С логической точки зрения, нет никаких оснований что шнурок должен быть одноразовым. Вместо того, чтобы добавлять здесь объекта waithandle, это также возможно, чтобы добавить связь между шнурком и объекта waithandle в точке использования. Самый простой способ-через экземпляр словаря.
Если вы можете переместить WaitHandle в свободную ассоциацию через карту в точке, где фактически используется WaitHandle, вы можете разорвать эту цепочку.
предупреждения
IDisposable
от распространения, вы должны попытаться инкапсулировать использование одноразового объекта внутри одного метода. Попробуйте спроектироватьShoelace
иначе:class Shoelace { bool tied = false; public void Tie() { using (var waitHandle = new AutoResetEvent(false)) { // you can even pass the disposable to other methods OtherMethod(waitHandle); // or hold it in a field (but FxCop will complain that your class is not disposable), // as long as you take control of its lifecycle _waitHandle = waitHandle; OtherMethodThatUsesTheWaitHandleFromTheField(); } } }
область действия дескриптора ожидания ограничена
Tie
метод, и классу не нужно иметь одноразовое поле, и поэтому не нужно будет быть одноразовым.так как дескриптор ожидания является деталью реализации внутри
Shoelace
, он не должен каким-либо образом изменить свою публику интерфейс, как добавление нового интерфейса в его объявление. Что произойдет тогда, когда вам больше не нужно одноразовое поле, вы удалитеIDisposable
декларации? Если вы думаете оShoelace
абстрагирование, вы быстро понимаете, что он не должен быть загрязнен инфраструктурными зависимостями, такими какIDisposable
.IDisposable
должны быть зарезервированы для классов, чьи абстракции инкапсуляции ресурс, который призывает к детерминированной очистки, т. е. для классов, где одноразовость является частью из абстрагирование.
Это очень похоже на проблему дизайна более высокого уровня, как это часто бывает, когда "быстрое исправление" переходит в трясину. Для более подробного обсуждения путей выхода, вы можете найти этой теме полезная.
интересно, если
Driver
определяется, как указано выше:class Driver { Shoe[] shoes = { new Shoe(), new Shoe() }; }
затем, когда
Shoe
выполненаIDisposable
, FxCop (v1. 36) не жалуется, чтоDriver
должно бытьIDisposable
.однако если он определен следующим образом:
class Driver { Shoe leftShoe = new Shoe(); Shoe rightShoe = new Shoe(); }
затем он будет жаловаться.
Я подозреваю, что это просто ограничение FxCop, а не решение, потому что в первой версии
Shoe
экземпляры все еще создаютсяDriver
и еще нужно чтобы как-то утилизироваться.
Я не думаю, что есть технический способ предотвратить распространение IDisposable, если вы держите свой дизайн так тесно связаны. Затем следует задаться вопросом, является ли дизайн правильным.
в вашем примере, я думаю, что имеет смысл иметь обувь собственный шнурок, и, возможно, водитель должен владеть его / ее обувь. Однако автобус не должен принадлежать водителю. Как правило, водители автобусов не следуют за автобусами на свалку:) в случае водителей и обуви, водители редко делают свою собственную обувь, это означает, что они на самом деле не "владеют" ими.
альтернативный дизайн может быть:
class Bus { IDriver busDriver = null; public void SetDriver(IDriver d) { busDriver = d; } } class Driver : IDriver { IShoePair shoes = null; public void PutShoesOn(IShoePair p) { shoes = p; } } class ShoePairWithDisposableLaces : IShoePair, IDisposable { Shoelace lace = new Shoelace(); } class Shoelace : IDisposable { ... }
новый дизайн, к сожалению, более сложен, поскольку он требует дополнительных классов для создания экземпляров и утилизации конкретных экземпляров обуви и драйверов, но это усложнение присуще решаемой проблеме. Хорошо, что автобусы больше не должны быть одноразовыми просто для того, чтобы избавиться от шнурков.
Это в основном то, что происходит, когда вы смешиваете композицию или агрегацию с одноразовыми классами. Как уже упоминалось,первым выходом было бы рефакторинг waitHandle из шнурка.
сказав это, вы можете значительно сократить одноразовый шаблон, когда у вас нет неуправляемых ресурсов. (Я все еще ищу официальную ссылку на это.)
но вы можете опустить деструктор и GC.SuppressFinalize(это); и, возможно, очистка виртуальной пустоты Утилизируйте (bool disposing) немного.
Как насчет использования инверсии управления?
class Bus { private Driver busDriver; public Bus(Driver busDriver) { this.busDriver = busDriver; } } class Driver { private Shoe[] shoes; public Driver(Shoe[] shoes) { this.shoes = shoes; } } class Shoe { private Shoelace lace; public Shoe(Shoelace lace) { this.lace = lace; } } class Shoelace { bool tied; private AutoResetEvent waitHandle; public Shoelace(bool tied, AutoResetEvent waitHandle) { this.tied = tied; this.waitHandle = waitHandle; } } class Program { static void Main(string[] args) { using (var leftShoeWaitHandle = new AutoResetEvent(false)) using (var rightShoeWaitHandle = new AutoResetEvent(false)) { var bus = new Bus(new Driver(new[] {new Shoe(new Shoelace(false, leftShoeWaitHandle)),new Shoe(new Shoelace(false, rightShoeWaitHandle))})); } } }