У меня есть маленькая иерархия объектов, которая в целом создается из данных в 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
конструктор должен быть частью базового класса и не должен быть вычислен (или доступный в) подклассыGenerateRaw ()
в оператор использования в теле CompositeNode
конструктор. Но повторение требования того призыва к каждому конструктору и неспособности гарантировать, что это выполняться для каждого подтипа когда-либо (a Node
не a Node
, семантически, без этих инициализированных свойств), дал мне ощущение неловкости, намного хуже, чем (потенциальная) утечка ресурсов здесь делает. CompositeNode
создал поток - CompositeNode
несет ответственность, если какой-либо другой код явно не заявляет, что он возьмет на себя это. Одним из вариантов здесь было бы, чтобы базовый конструктор разрешил это через защищенную перегрузку, но порядок означает, что было бы трудно точно знать, когда его можно безопасно удалить. Это можно сделать с помощью виртуального метода
, но на самом деле вам не следует вызывать виртуальные
методы в конструкторах.
Интересно, будет ли лучше рефакторинг с использованием метода initialize ( Load
) (который вы вызываете отдельно для построения). Возможно, это защищенный виртуальный метод
и раскрытие его с помощью общедоступного статического
метода.
Вы можете рассмотреть возможность передачи инструкций относительно размещения в виде / в отдельном аргументе конструктора, принимающего IDisposable. Это подход, применяемый XmlReader.Create, который принимает параметр XmlReaderSettings, свойство CloseInput которого определяет, удаляется ли базовый источник данных при окончательном удалении созданного XmlReader.
Для этого простого примера я бы использовал метод 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; } }
}
Основное практическое правило состоит в том, что код, создающий экземпляр одноразового объекта, должен его удалить. Если у вас есть объект IDisposable
, переданный в метод, вы должны использовать его для того, что вам нужно, и оставить его в покое.
Хороший способ убедиться, что вы всегда это делаете, - использовать шаблон using ([IDisposable object]) {...}
, который автоматически вызовет dispose для объекта, когда область видимости будет завершена.