Передача IDisposable возражает через цепочки конструктора

У меня есть маленькая иерархия объектов, которая в целом создается из данных в a 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;
    }
}

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

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

Превентивные удары:

  • да, свойства, вычисленные в Node конструктор должен быть частью базового класса и не должен быть вычислен (или доступный в) подклассы
  • Я не потребую, чтобы поток был передан в CompositeNode (его формат должен быть не важен вызывающей стороне),
  • Предыдущее повторение имело вычисление значения в базовом классе как отдельный защищенный метод, который я затем просто назвал в конце каждого конструктора подтипа, двигал телом GenerateRaw () в оператор использования в теле CompositeNode конструктор. Но повторение требования того призыва к каждому конструктору и неспособности гарантировать, что это выполняться для каждого подтипа когда-либо (a Node не a Node, семантически, без этих инициализированных свойств), дал мне ощущение неловкости, намного хуже, чем (потенциальная) утечка ресурсов здесь делает.
6
задан Matt Enright 7 June 2010 в 11:20
поделиться

4 ответа

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

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

4
ответ дан 17 December 2019 в 04:42
поделиться

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

2
ответ дан 17 December 2019 в 04:42
поделиться

Для этого простого примера я бы использовал метод 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
ответ дан 17 December 2019 в 04:42
поделиться

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

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

-1
ответ дан 17 December 2019 в 04:42
поделиться
Другие вопросы по тегам:

Похожие вопросы: