何时不应该实现IDisposable接口?

3

首先,如果这被认为是重复内容,我很抱歉 - 我知道这是一个常见话题,但我已经查阅了很多,没有找到令人满意的答案。

有很多问题在询问何时使用 IDisposable,但从我所读的所有内容来看,我就是看不出为什么你不会在你创建的每个类中都实现它。有什么可以失去的?它会对性能产生很大影响吗?

我的理解之一是:IDisposable 的作用之一是: Dispose() 所拥有的对象的其他 IDisposables。请原谅我的无知,这是仅适用于给定对象的字段/属性还是也扩展到其方法中创建的对象呢?

例如,如果在实现 IDisposable 的类中的方法内创建了一个 Font,但该 Font 没有使用 using 块进行初始化或在方法末尾显式调用 .Dispose(),那么当它的 IDisposable 父对象/类被 GCd/disposed 时,它会被处理吗?否则,Font 将永远不会被处理吗?

我不是要离题,但如果它确实像这样充当 'catch all'(有效地处置任何会被遗漏而未处置的子 IDisposable 对象),那么这就足以证明始终在可能情况下实现 IDisposable 了吗?


好的,如果您需要释放资源......请遵循已经建立的模式。实现 IDisposable - Simon Whitehead
1
请参考这个问题,它提出了相反的问题。IDisposable 应该只在真正需要时实现(特别是因为如何正确实现它并不明显)。https://dev59.com/mUjSa4cB1Zd3GeqPFGvD - Robert Levy
通常情况下,我发现我很少需要保留长期使用的IDisposable实例,特别是在使用async/await时,因此为每个实例实现IDisposable将是过度杀伤力的。并非所有的类都是UI类。 - spender
1
@Alfie:如果您在方法中创建字体而没有进行处理,从而丢失了对IDisposable对象的引用,那么您将无能为力。如果正确实现了丢失的IDisposable,则GC对终结器的调用应该可以清理掉它。 - spender
@Alfie:很简单。在本地范围内声明一个变量,而不将其存储在字段(或属性)中,在范围结束时(例如方法的结尾),您将失去对它的引用。从现在开始,您的代码无法访问该对象。最后一次机会是当GC决定收集对象时,如果实现了finalizer,则会调用它(在大多数情况下)。您失去了对finalizer代码何时运行的任何控制,这可能是不可取的。 - spender
显示剩余4条评论
3个回答

9
规则非常简单,如果你的类中有任何是可释放类型的字段,那么你需要实现IDisposable 接口,以便于能够对它们进行处理。
方法内部发生的事情与类的字段并没有太大关系。如果你创建了字体并将其存储在字段中,那么按照上述规则,你需要一个Dispose()方法,如果你只是使用字体去绘制一些东西,就像通常所做的那样,那么请始终使用using语句,在使用完后立即释放字体。

谢谢@HansPassant,这很有帮助。所以我可以得出结论,包含类的Dispose()方法对在其他方法中创建的变量没有影响(在类实例的生命周期内)?在这种情况下不使用usingDispose()创建的Font将消失? - Alfie
你需要自己编写Dispose()方法,没有任何自动魔法可言。而且,在Dispose()方法中,你不能访问另一个方法的局部变量。那些变量早已消失无踪。 - Hans Passant
我以为是这样的 - 感谢确认。但是实现IDisposable不意味着它的子字段和可释放的字段将自动调用其Dispose(),而不必在类的Dispose方法中显式调用它们吗? - Alfie
@Alfie:不,它不会。 - spender
3
请注意,“have any fields”有些错误,“have any fields and own objects”可能更好。 某些情况下,对象具有包含“IDisposable”的字段,但对象不拥有该“IDisposable”。 在这种情况下,您不能在对象内部处理“IDisposable”(并且可能不应在该对象上实现“IDisposable”)。 示例可能是包装器类,它将参数打包到方法中(例如,在某个“CopyTo”方法的参数中使用“{destinationName,stream}”) 。 - Alexei Levenkov
@AlexeiLevenkov: 我希望 .NET 有不同的类型,比如“封装对象所有权的引用”,“封装身份但不包含所有权的可持续引用”,“不封装所有权的短暂引用”等等。如果类型系统能够适应这一点,那么像集合之类的东西就可以更智能地处理类似于 EqualsGetHashCode 的问题了。 - supercat

1
大多数对象不需要它。框架会很好地处理垃圾回收。COM对象和图形对象是需要实现IDisposable以进行良好清理的对象之一。是的,当回收对象时可能会有一些固有的性能损失。

0
如果一个对象知道在宇宙末日之前需要发生某些事情,而没有其他东西具有必要的知识和动力来确保这些事情得以完成,那么该对象应该实现IDisposableDispose方法让对象知道它最好立即执行这些操作,否则它们可能永远不会被执行。
如果派生或实现该类型的实例可能知道在宇宙末日之前需要发生某些事情,并且持有引用的最后一个实体很可能知道它是从抽象类型或接口类型派生或实现的实例,而不是作为实现实现IDisposable的更具体类型的实例,则应该实现IDisposable
实现继承IDisposable的接口的类型将需要实现IDisposable,无论是否有任何其他原因需要这样做。
有理由实现IDisposable的类型应该这样做。没有理由的类型则不应该这样做。 补充说明 为了理解为什么不应该总是实现IDisposable接口,考虑实际的成本可能会有所帮助。处理器调用一个无操作的Dispose方法所需的时间微不足道,但这并不是真正需要考虑的问题。更大的问题在于,大多数对象都属于以下四种类型之一:
  • 封装资源并需要有一个明确定义的所有者的对象

  • 封装可变状态并需要有一个明确定义的所有者的对象

  • 不需要有明确定义的所有者的不可变类型的对象

  • 将永远不会暴露给可能会改变它们的代码的可变类型的实例,并且不需要有明确定义的所有者。

正确使用可变对象通常需要跟踪谁拥有它们,就像持有资源的对象一样。同样,如果一个具有可变状态的对象被用来封装另一个对象的可变状态,那么正确使用后者对象将需要跟踪谁拥有它,就像封装拥有资源的其他对象一样。可变类型实例用于封装不可变对象的状态(确保该实例永远不会暴露给可能会改变它的代码),与持有资源的对象相比,其区别在于不再需要跟踪谁拥有它。相反,即使这些对象在语义上是不可变的,也需要跟踪封装其他持有资源对象的对象的所有权。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接