保守的 ID是接口上的可标记

本文关键字:ID 接口 | 更新日期: 2023-09-27 18:19:54

在可能需要或不需要处置接口的实现的情况下,要求必须通过将接口本身标记为IDisposable来释放所有实现是否是一个好主意?

这会强制此接口的用户始终释放对象,即使对于某些实现可能是不必要的。 目标显然是确保实现始终可以无缝交换。 另一种方法是将其留给用户决定,这似乎更正确,但更难沟通。 另一方面,考虑到用户只有一个用接口类型声明的符号,要求无条件释放它似乎更正确。 这种推理是否存在权衡或谬误?

如果问题过于依赖于情况,我想将其置于教科书存储库(存储库设计模式(的上下文中,其中大多数已知的实现都需要处理。

保守的 ID是接口上的可标记

如果问题过于依赖于情况,我想将其置于教科书存储库(存储库设计模式(的上下文中,其中大多数已知的实现都需要处理。

如果您正在编写实例化对象并负责其生存期的库代码,那么理想的方案是处理IDisposable,但不要要求它,除非大多数实现极有可能需要清理;例如,在代码完成未知对象时使用 isas。它很少像这样简单,但例如:

IFoo obj = (IFoo)Activator.CreateInstance(unknownType);
using(obj as IDisposable)
{
    // TODO: code
}

在决定IFoo是否应该实现IDisposable时,关键问题不应该是实现IFoo的很大一部分类型是否需要处置,甚至不是实现IFoo的对象实例的很大一部分需要处置,而是是否需要处置由返回类型为 IFoo 的工厂方法返回的任何非平凡的实例由该工厂方法的调用方提供

考虑IEnumerator . 只有一小部分实现它的类需要处置(随着迭代器的引入,这个部分可能会显着增加(,但绝大多数实例将通过调用工厂方法IEnumerable.GetEnumerator()获得,其返回类型为 IEnumerator 。 调用IEnumerable.GetEnumerator()的代码通常不知道返回的对象是否需要处置;对于语义正确性,除非代码知道它使用的IEnumerable类型不会返回需要处置的实例,否则为了正确性,它必须尝试将返回的实现强制转换为IDisposable,如果结果为非 null,则释放它们。

当Microsoft实现IEnumerator<T>时,他们意识到消费者必须尝试将枚举器转换为IDisposable的负担远远大于实施者必须实现无所事事Dispose方法的负担。 语义正确性要求消费者在任何实现IDisposable的枚举器上调用Dispose,并且无条件地在已知实现它的类型上调用无所事事Dispose方法比测试可能实现IDisposable的类型是否这样做更容易和更快。

即使只有1%的IEnumerator<T>实现实现了IDisposable,为了语义正确性,绝大多数消费者必须假设他们中的任何一个都可能。 拥有IEnumerable<T>继承IDisposable不会给消费者带来任何他们本来不会承担的义务——它只是让他们更容易履行他们在任何情况下都会承担的义务。

因此,您正在公开一个接口

ISomeInterface

如果你打算用这个接口来触发处置,即你想调用Dispose,那么,它应该实现IDisposable

ISomeInterface : IDisposable

但是,考虑到无处不在的IDisposable的组合肯定会导致ISomeInterface实现的第 3 方用户显式或using块调用Dispose themsevles。

如果您不打算自己调用Dispose,请将选择权留给具体的类实现者。只有他们才能决定在特定情况下是否需要处置。


现在我可以引用一个权威来源,

除非绝大多数实现极有可能需要清理,否则不要要求它

我认为,在Stream的例子中,这是"绝大多数可能性"。

最近再次思考这个问题后,我想分享另一个准则/经验法则,如果适用,可能会使决策更容易。

还没有人考虑的是用户/实现的比例:

    如果用户多于实现,
  • 特别是如果实现都在您的控制之下,请考虑将接口标记为IDisposable并强制用户始终释放,即使不是绝对必要的。只要这感觉适合手头的用例,它很可能会使用户的生活更简单:他们不需要检查是否需要处理,他们会系统地进行处理。缺点是您必须为那些不需要它的实现实现虚拟Dispose方法。除非你有特殊的性能要求,否则这个调用是完全可以的(我想唯一的开销是虚拟表查找,方法本身可能被优化了(。

  • 如果实现多于用户,例如在插件系统的情况下,请考虑验证对象是否在核心中动态实现IDisposable,以便实现永远不必实现虚拟IDisposable方法。缺点是核心中需要额外的逻辑。开销是强制转换和空检查的开销。

dr:我们经常假设用户多于实现。情况并非总是如此。

当然,这只是问题的一个维度,可以用来决定;另一个维度是需要处理的实现和不需要处理的实现之间的比率。 有关这方面的讨论,请参阅其他答案。

一旦你评估了每一个,立即考虑所有因素,用你的意见加盐,然后做出决定。