【问题标题】:Does implementing IDisposable contradict composition aggregation?实现 IDisposable 是否与组合聚合相矛盾?
【发布时间】:2018-09-26 11:52:45
【问题描述】:

在 UMLs composition 聚合中,我倾向于使用 IDisposable 接口/模式来强制 "Child 不能在没有 Parent 的情况下存在" - 要求组合要求:

public class Parent : IDisposable
{
    private readonly _childs;
    public IReadOnlyCollection<Child> Childs => _childs;

    public Parent()
    {
        _childs = new List<Child>();
    }

    public Child CreateChild()
    {
        var child = new Child();
        _childs.Add(child);
        return child;
    }

    public void Dispose()
    {
        foreach (var child in _childs)
            child.Dispose();
    }
}

public class Child : IDisposable
{
    internal Child() {}

    public void Dispose()
    {
         //Cleanup object, imagine an idempotent implementation
    }
}

到目前为止,一切都很好。但现在想象一下这段代码:

var parent = new Parent();
var child = parent.CreateChild();
child.Dispose();
//At this point parent.Childs contains a disposed child object

由于我目前在我开发的库中面临这样的情况,我想到了几个问题:

  • 可以吗,parent.Childs 包含一个(实际上)不可用的对象?
    • 如果是
      • 您是否会忽略它,因为过早处置它是用户自己的决定?
    • 如果没有
      • 在如何处理 C# 中子对象的过早处理方面是否有某种最佳实践?我的第一个想法是在处置子对象时使用回调函数/委托将自己从活动实例列表中删除,但对我来说这听起来相当笨拙。
      • 还有其他借口可以让我免去责任吗?

从架构的角度来看,主要问题是,每个能够获得Child 实例的人都可以看到IDisposable。隐藏它基本上意味着利用 OO 多态性并将处置能力提取到一个不可见的实现中。但是对于域模型中的许多类,这绝对是一个臃肿的因素,没有额外的好处。此外,它固有地将 UML 组合聚合解释为 "Child 只能Parent" 破坏,我认为这是不正确的:

public interface IChild
{
    //Child methods
}

internal class Child : IChild, IDisposable
{
    //See implementation above
}

public class Parent : IDisposable
{
    public IReadOnlyCollection<IChild> Childs => _childs;

    public IChild CreateChild()
    {
        var child = new Child();
        _childs.Add(child);
        return child;
    }
}

【问题讨论】:

  • 我可能会使用IsDisposed 事件(自行实现),在子项被处置时将其从父项中移除。
  • 这似乎是使用IDisposable 的常用方式,但每个人都不清楚(不看类 Disposable 模式)会发生什么
  • 一种选择是在Child 中使IDisposable 的接口实现显式:void IDisposable.Dispose() - 然后调用者必须将孩子强制转换为IDisposable 才能处理它,即((IDisposable)child).Dispose();
  • @stuartd 这对using 语句没有帮助,因为仍然允许使用这些语句。我也想到了它,但我似乎很容易出错。
  • 这是一个有趣的问题,虽然是关于具体实现,但与此问题密切相关:*.com/questions/50912160/…

标签: c# .net architecture uml


【解决方案1】:

可以吗,parent.Childs 包含一个(实际上)不可用的对象?

是的,但你不应该再碰它。通常你会在处理它时抛出一个ObjectDiposedException

我的第一个想法是在处置子对象时使用回调函数/委托将自己从活动实例列表中删除,但对我来说这听起来相当笨拙。

确实笨拙而危险。想象一下,您有一组财务数据,突然一些对象被删除,因为一些开发人员错误地处置了一个对象。而是如上所述抛出异常。

剩下的问题是:子对象应该实现IDisposable,还是应该有一个只有父类知道和可用的“dispose”方法?这种方法对我来说似乎更有意义。

【讨论】:

  • 我认为这是对“忽略它,因为用户自己决定过早处置它”的投票。只有父类知道和可用的 Dispose-Method 基本上在第二个代码示例中演示。我不知道是否值得额外的复杂性。
  • 然后再读一遍。这是一个投票:让你的软件健壮,这样你就不会陷入这种情况。
  • 嗯,我不知道我的评论在哪里会让你感到冒犯,但我不是故意这样做的。使软件健壮是另一个范围,这个问题基本上归结为如何将架构抽象转换为(C#)代码。在问题的范围内,不删除对死对象的引用是绝对有效的替代方案,恕我直言。
  • @PeterWurzinger 嗯,我不知道我的评论在哪里会让你觉得冒犯,但我不是故意这样做的。 ;)
  • 使软件健壮不是另一个范围。这是你问题的核心。您可以通过使用和公开破坏程序的接口来允许某些事情。那就不要了,然后搜索其他选项。