Начните с этих простых занятий ...
Скажем, у меня есть простой набор таких классов:
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
имеет a Driver
, Driver
имеет два Shoe
s, каждый Shoe
имеет a 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
на 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(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
, но при этом обеспечить правильную утилизацию неуправляемых объектов?
источник
Ответы:
Вы не можете "предотвратить" распространение IDisposable. Некоторые классы нужно удалить, например
AutoResetEvent
, и наиболее эффективный способ - сделать это вDispose()
методе, чтобы избежать накладных расходов на финализаторы. Но этот метод должен быть вызван каким-то образом, поэтому точно так же, как в вашем примере классы, которые инкапсулируют или содержат IDisposable, должны избавляться от них, поэтому они также должны быть одноразовыми и т. Д. Единственный способ избежать этого - это:using
шаблона)В некоторых случаях IDisposable можно игнорировать, поскольку он поддерживает необязательный регистр. Например, WaitHandle реализует IDisposable для поддержки именованного Mutex. Если имя не используется, метод Dispose ничего не делает. Другой пример - MemoryStream, он не использует системные ресурсы, а его реализация Dispose также ничего не делает. Тщательное обдумывание того, используется ли неуправляемый ресурс или нет, может быть полезным. Так же можно изучить доступные источники для библиотек .net или с помощью декомпилятора.
источник
С точки зрения корректности, вы не можете предотвратить распространение IDisposable через объектные отношения, если родительский объект создает дочерний объект, который теперь должен быть одноразовым, и фактически владеет им. FxCop верен в этой ситуации, и родитель должен быть IDisposable.
Что вы можете сделать, так это избежать добавления IDisposable к конечному классу в вашей иерархии объектов. Это не всегда легкая задача, но это интересное упражнение. С логической точки зрения нет причин, по которым шнурки ShoeLace должны быть одноразовыми. Вместо добавления здесь WaitHandle можно также добавить связь между ShoeLace и WaitHandle в том месте, где она используется. Самый простой способ - использовать экземпляр Dictionary.
Если вы можете переместить 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
должны быть зарезервированы для классов, абстракция которых инкапсулирует ресурс, требующий детерминированной очистки; т. е. для классов, где доступность является частью абстракции .источник
AutoResetEvent
используется для связи между разными потоками, которые работают в одном классе, поэтому он должен быть переменной-членом. Вы не можете ограничить его объем одним методом. (например, представьте, что один поток просто сидит, ожидая некоторой работы, блокируя егоwaitHandle.WaitOne()
. Затем основной поток вызываетshoelace.Tie()
метод, который просто выполняетwaitHandle.Set()
и немедленно возвращается).Это в основном то, что происходит, когда вы смешиваете Composition или Aggregation с Disposable классами. Как уже упоминалось, первым выходом было бы реорганизовать waitHandle из шнурка.
Сказав это, вы можете значительно сократить шаблон Disposable, если у вас нет неуправляемых ресурсов. (Я все еще ищу официальную ссылку для этого.)
Но вы можете опустить деструктор и GC.SuppressFinalize (this); и, возможно, немного очистить виртуальную пустоту Dispose (bool dispose).
источник
Интересно, если
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
и их нужно как-то утилизировать.источник
Shoe
конструкторов возникло исключение, тоshoes
он остался бы в сложном состоянии?Я не думаю, что есть технический способ предотвратить распространение 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 { ... }
К сожалению, новый дизайн более сложен, поскольку он требует дополнительных классов для создания и удаления конкретных экземпляров обуви и драйверов, но это усложнение является неотъемлемой частью решаемой проблемы. Хорошо то, что автобусы больше не нужно использовать для одноразового использования только для того, чтобы избавиться от шнурков.
источник
Как насчет использования инверсии управления?
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))})); } } }
источник