Иерархия классов, использующих одноразовый объект. Внедрить IDisposable для всех из них?

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

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

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

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

Я думаю, мне неудобно ломать IDisposable семантику в классы, которые не нуждаются в них, кроме небольшого небольшого пути реализации вниз по цепочке. Существуют ли какие-либо способы этого?

Ответ 1

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

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

Ответ 2

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

Ответ 3

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

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

Например:

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.

Ответ 4

Вам нужна реализация 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() рассматривается как даже смутно правдоподобная.

Ответ 5

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

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

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

Ответ 6

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