通过构造函数链传递IDisposable对象

我有一个小的对象层次结构,通常是从Stream数据构造的,但是对于某些特定的子类,可以从更简单的参数列表中合成。 在从子类链接构造函数时,我遇到了一个问题,即确保处理基类构造函数所需的合成流。 它没有逃过我的说法,这种方式使用IDisposable对象可能只是脏池(plz建议?),原因我没有考虑过,但是,除了这个问题外,它似乎相当简单(并且封装良好)。

代码:

 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公民身份不好的情况,但它仍然给了我heebie-jeebies(更不用说我可能并不总是使用MemoryStream用于其他子类型)。 它不在范围内,所以我不能在构造函数中稍后显式Dispose () ,并且在GenerateRaw ()添加using语句会弄巧成拙,因为我需要返回流。

有一个更好的方法吗?

先发制人的罢工:

  • 是的, Node构造函数中计算的属性应该是基类的一部分,不应该由子类计算(或可以在子类中访问)
  • 我不会要求将流传递给CompositeNode(其格式应与调用者无关)
  • 上一次迭代将基类中的值计算作为一个单独的受保护方法,然后我在每个子类型构造函数的末尾调用,将GenerateRaw ()的主体移动到CompositeNode构造函数体中的using语句中。 但重复要求调用每个构造函数而不能保证它为每个子类型运行(一个Node不是一个Node ,在语义上,没有初始化这些属性)给了我heebie-jeebies远比(潜在的) )资源泄漏在这里。

CompositeNode创建了流 – CompositeNode负有责任,除非其他一些代码明确声明它将接受它。 这里的一个选择是基础构造函数通过受保护的重载来允许它,但是排序意味着很难确切地知道何时可以安全地处理它。 virtual方法允许您执行此操作,但您不应该在构造函数中真正调用virtual方法。

我想知道是否重构以便有一个初始化Load )方法(你单独调用构造)会更好。 也许是protected virtual方法,并通过public static方法公开它。

您可能需要考虑将有关处置的指令作为/在接受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); } } } 

如果您有更深层次结构,则将其设为虚拟。 我倾向于发现这样的代码有点难以跟踪,并使用我在完整的库/框架代码中看到的模式:分裂成普通对象(最好是不可变的,不知道创建它们的是什么,例如仅从其成员中)和创建它们的读者(或数据不是来自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 here, but you can store whatever. public CompositeNode(IEnumerable values) : Node(NodeKind.Composite) { this.values = values.ToArray(); } public IEnumerable { get { return filename; } } } 

主要的经验法则是创建一次性对象实例的代码应该处理它。 如果您将IDisposable对象传递给方法,则应将其用于所需内容,并将其单独使用。

确保始终执行此操作的一种好方法是使用using ([IDisposable object]) { ... }模式,该模式将在范围完成时自动调用对象上的dispose。