Передача IDisposable объектов через цепочки конструктора - PullRequest
6 голосов
/ 07 июня 2010

У меня есть небольшая иерархия объектов, которая обычно создается из данных в Stream, но для некоторых конкретных подклассов может быть синтезирована из более простого списка аргументов. При объединении конструкторов из подклассов я сталкиваюсь с проблемой обеспечения удаления синтезированного потока, который необходим конструктору базового класса. Не ускользнуло от меня тот факт, что использование IDisposable объектов таким способом, возможно, является просто грязным пулом (пожалуйста, посоветуете?) По причинам, которые я не рассмотрел, но, если оставить в стороне эту проблему, она кажется довольно простой (и с хорошей инкапсуляцией).

Коды:

abstract class Node {
    protected Node (Stream raw)
    {
        // calculate/generate some base class properties
    }
}
class FilesystemNode : Node {
    public FilesystemNode (FileStream fs)
        : base (fs)
    {
        // all good here; disposing of fs not our responsibility
    }
}
class CompositeNode : Node {
    public CompositeNode (IEnumerable some_stuff)
        : base (GenerateRaw (some_stuff))
    {
        // rogue stream from GenerateRaw now loose in the wild!
    }

    static Stream GenerateRaw (IEnumerable some_stuff)
    {
        var content = new MemoryStream ();
        // molest elements of some_stuff into proper format, write to stream
        content.Seek (0, SeekOrigin.Begin);
        return content;
    }
}

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

Есть ли лучший способ сделать это?

Упреждающие удары:

  • да, свойства, рассчитанные в конструкторе Node, должны быть частью базового класса и не должны рассчитываться (или доступны в) подклассами
  • Я не буду требовать, чтобы поток передавался в CompositeNode (его формат не должен относиться к вызывающей стороне)
  • На предыдущей итерации вычисление значения в базовом классе выполнялось как отдельный защищенный метод, который я затем просто вызывал в конце каждого конструктора подтипа, перемещая тело GenerateRaw () в оператор using в теле CompositeNode конструктор. Но повторение требования этого вызова для каждого конструктора и неспособность гарантировать, что он будет выполняться для каждого подтипа когда-либо (Node не является Node, семантически, без инициализации этих свойств), сделало меня гораздо хуже чем (потенциальная) утечка ресурсов здесь.

Ответы [ 4 ]

4 голосов
/ 07 июня 2010

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

Интересно, будет ли лучше рефакторинг, чтобы был initialize (Load) метод (который вы вызываете отдельно для конструкции) Возможно, метод protected virtual, и разоблачить его с помощью метода public static.

2 голосов
/ 07 июня 2010

Вы можете рассмотреть возможность передачи инструкций относительно расположения как / в пределах отдельного аргумента конструктора, который принимает IDisposable. Это подход XmlReader.Create, который принимает параметр XmlReaderSettings, свойство CloseInput которого определяет, будет ли удален базовый источник данных, когда созданный XmlReader в конечном итоге будет удален.

0 голосов
/ 07 июня 2010

Для этого простого примера я бы использовал метод InitializeFrom(Stream s):

abstract class Node
{
    public Node(Stream stream) { InitializeFrom(stream); }
    protected Node() { }
    protected void InitializeFrom(Stream stream);
}

class FilesystemNode
{
    public FilesystemNode(FileStream stream) : base(stream) {}
}

class CompositeNode
{
    public CompositeNode(IEnumerable values) : base()
    {
        using (var stream = new MemoryStream())
        {
            // init stream
            InitializeFrom(stream);
        }
    }
}

Сделать его виртуальным, если у вас более глубокая иерархия. Я склонен находить код, подобный этому, немного сложным для отслеживания, и использую шаблон, который я видел в полных кодах библиотеки / фреймворка: разбиение на простые объекты (желательно неизменные и неосведомленные о том, что их создает, например, только от их членов ) и устройства чтения (или фабрики, если данные не поступают из потока), которые их создают, но промежуточный уровень - это метод статического чтения:

abstract class Node
{
    NodeKind kind;
    public Node(NodeKind kind) { this.kind = kind; }
    public NodeKind Kind { get { return kind; } }

    static Node ReadFrom(Stream stream);
}

class FilesystemNode : Node
{
    string filename;
    public FilesystemNode(string filename) : Node(NodeKind.Filesystem)
    {
        this.filename = filename;
    }
    public string Filename { get { return filename; } }

    static FilesystemNode ReadFrom(FileStream stream);
}

class CompositeNode : Node
{
    Node[] values;
    // I'm assuming IEnumerable<Node> here, but you can store whatever.
    public CompositeNode(IEnumerable<Node> values) : Node(NodeKind.Composite)
    {
        this.values = values.ToArray();
    }
    public IEnumerable<Node> { get { return filename; } }
}
0 голосов
/ 07 июня 2010

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

Хороший способ убедиться, что вы всегда делаете это, это использовать шаблон using ([IDisposable object]) { ... }, который будет вызывать dispose для объекта автоматически, когда область будет завершена.

...