Иерархия классов, использующих одноразовый предмет.Реализовать ID можно на всех? - PullRequest
10 голосов
/ 18 ноября 2011

У меня есть класс, который использует файловый поток. Он должен закрыть поток, когда приложение закрывается, поэтому я заставляю класс реализовать IDisposable.

Этот класс является членом другого класса, который является членом другого класса и т. Д. Вплоть до моего основного приложения.

Должен ли я поэтому реализовать IDisposable на всех этих классов?

Что если в будущем я изменю свою файловую реализацию, чтобы она закрывала файл после каждой записи? Теперь у меня есть целый набор классов, которые реализуют IDisposable без причины.

Полагаю, мне неудобно ломать семантику IDisposable в классы, в которых они не нужны, кроме небольшой детализации реализации в цепочке. Есть ли способы обойти это?

Ответы [ 6 ]

4 голосов
/ 18 ноября 2011

В общем случае, если ваш тип содержит член, который реализует IDisposable, тип также должен реализовывать IDiposable. Это самый простой способ применения шаблона IDisposable.

Единственное исключение, которое я использую, заключается в том, что в моем контракте типов содержится метод, который 1) должен быть вызван и 2) сигнализирует об окончании использования ресурса IDisposable. В этом случае я чувствую себя комфортно, не реализуя IDisposable и вместо этого использую этот метод для вызова Dispose

2 голосов
/ 18 ноября 2011

Если вы явно хотите избавиться от файлового потока, тогда да, вам нужно реализовать IDisposable для любых классов, которые содержат ссылку на ваш IDisposable.Если разумно распоряжаться файловым потоком после каждой записи, то есть не снижать производительность из-за частых запусков, это звучит предпочтительнее.

1 голос
/ 18 ноября 2011

Как правило, когда тип сохраняет ссылку на IDisposable в поле экземпляра, я также делаю его одноразовым. Но я обычно стараюсь не оказаться в этой ситуации; когда это возможно, я стараюсь утилизировать одноразовые материалы тем же способом, в котором они были созданы, с блоком using.

1 голос
/ 18 ноября 2011

В любой данный момент каждый экземпляр каждого типа, который реализует IDisposable, должен иметь по крайней мере одну (и обычно ровно одну) сущность, которая может вызвать вызов Dispose через некоторое время после того, как он больше не нужен, и до того, как он станет полностью и в конечном итоге заброшен. Если в вашем типе есть поле типа IDisposable, но можно ожидать, что что-то еще может избавиться от любого экземпляра IDisposable, на который он может ссылаться, то вам не следует вызывать Dispose для этого поля самостоятельно. Если у вашего типа есть поле IDisposable, никто не будет использовать этот объект, как только вы покончили с ним, и никто не должен его утилизировать, тогда вы должны вызывать Dispose для объекта, когда он вам больше не нужен. Во многих случаях ваш объект будет нуждаться в другом объекте до тех пор, пока ваш объект не понадобится ни одному другому объекту, и способ, которым ваш объект обнаружит это, - это когда кто-то еще вызывает Dispose для него (после чего он будет вызывать Dispose для других объектов).

Один шаблон, который иногда может быть полезен, состоит в том, чтобы класс выставлял событие Disposed, которое возникает при каждом вызове Dispose. Это может быть полезно, если, например, другой объект дает вашему объекту ссылку на IDisposable, которая ему потребуется некоторое время, а затем объект, предоставивший вам IDisposable, завершает работу с ним. Он не может утилизировать объект, пока ваш объект все еще нуждается в нем, и ваш объект не собирается его утилизировать (поскольку ваш объект не будет знать, сделано ли с ним объект, предоставивший IDisposable). Если класс, предоставивший вашему классу IDisposable, перехватывает обработчик Disposed вашего объекта, то обработчик событий может затем заметить, что вашему объекту больше не нужен IDisposable, и либо немедленно его удалить (если ваш объект был последним, который нуждался в нем), либо установите флаг так, чтобы, когда другой пользователь закончил работу с объектом, он был удален).

Еще один шаблон, который может быть полезен, если у вашего объекта будет определенный набор одноразовых объектов, которые он будет хранить в течение всей своей жизни, - это хранить список объектов IDisposable, а затем метод Dispose итерировать по списку и размещать в нем объекты. Каждый элемент в списке должен быть расположен в своем собственном блоке Try / Catch; если возникает исключение, выдается исключение CleanupFailureException (пользовательский тип), которое имеет первое или последнее такое исключение, как его InnerException, а также содержит список всех исключений, которые возникли как пользовательское свойство.

1 голос
/ 18 ноября 2011

Вам нужна реализация IDisposable в каждом, но это не обязательно требует явной реализации в коде каждого.Пусть наследование сделает всю работу за вас.

Два подхода:

class FileHandlingClass : IDisposable
{
  private FileStream _stm;
  /* Stuff to make class interesting */
  public void Disposable()
  {
    _stm.Dispose();
  }
  /*Note that we don't need a finaliser btw*/
}

class TextHandlingClass : FileHandlingClass
{
  /* Stuff to make class interesting */
}

Теперь мы можем сделать:

using(TextHandlingClass thc = new TextHandlingClass())
  thc.DoStuff();

и т. Д.

Это всеработает, потому что TextHandlingClass наследует единственную реализацию IDisposable, которая ему когда-либо понадобится.

Это становится хитрее, если у нас есть дальнейшие потребности в утилизации:

Скажем, у нас есть класс, которыйобрабатывает пул XmlNameTable объектов (почему это хорошая идея для другого потока) и утилизирует его, возвращает таблицу в пул, и она используется XmlHandlingClass.Теперь мы можем справиться с этим в некоторой степени с:

class XmlHandlingClass : FileHandlingClass, IDisposable
{
  PooledNameTable _table;
  /* yadda */
  public new void Dispose() // another possibility is explicit IDisposable.Dispose()
  {
    _table.Dispose();
    base.Dispose();
  }
}

Теперь это прекрасно работает с:

using(XmlHandlingClass x = new XmlHandlingClass())
  x.Blah();

, но не с:

using(FileHandlingClass x = new XmlHandlingClass())
  x.Blah()

В последнем случае используется только реализация FileHandlingClass (к счастью, не возвращение объединенной таблицы имен в пул является второстепенным вопросом, большинство случаев Dispose() гораздо важнее).Следовательно, если возможны переопределения, мы должны сделать:

//Allow for Disposal override
class FileHandlingClass : IDisposable
{
  private FileStream _stm;
  /* Stuff to make class interesting */
  public virtual void Disposable()
  {
    _stm.Dispose();
  }
  /*Note that we don't need a finaliser btw*/
}

//Still don't care here
class TextHandlingClass : FileHandlingClass
{
  /* Stuff to make class interesting */
}

class XmlHandlingClass : FileHandlingClass
{
  PooledNameTable _table;
  /* yadda */
  public override void Dispose()
  {
    _table.Dispose();
    base.Dispose();
  }
}

Теперь у нас гораздо больше безопасности в вызовах Dispose(), но нам все же нужно только реализовать это сами, где это важно.

Во втором случае есть минутный удар по производительности, но он действительно минутный.Я указываю на это сейчас только для того, чтобы выступать против рассмотрения первого в любом случае, когда необходимость переопределения Dispose() представляется даже смутно правдоподобной.

1 голос
/ 18 ноября 2011

Это зависит от того, как вы реализуете класс, который использует файловый поток. Если этот класс создает файловый поток, то он должен отвечать за его удаление. Однако, если вы измените его так, чтобы метод принимал файловый поток в качестве параметра, он больше не будет «владеть» файловым потоком и, следовательно, не будет нести ответственность за его удаление.

Если класс является частью некоторой иерархии, вы можете просто добавить файловый поток в качестве параметра, начинающегося сверху, и представить его всем методам вплоть до того места, где он фактически используется.

Например:

public class Class1
{
    private readonly Class2 SomeObject = new Class2();

    public void DoWork1(Filestream stream)
    {
        SomeObject.DoWork2(stream);
    }
}

public class Class2
{
    public void DoWork2(Filestream stream)
    {
        // Do the work required with the Filestream object
    }
}

Хотя я не уверен, что сам использовал бы этот шаблон, это позволит вам не добавлять «IDisposable» ни к каким классам, кроме того, который первоначально создал объект Filestream.

...