添加IDisposable接口如何改变类的语义?

4

IDisposable的MSDN文档说明:

向现有类添加IDisposable接口是一种破坏版本的更改,因为它改变了类的语义。

这到底意味着什么?

我可以看出来,删除 IDisposable 将是一个大问题。例如,在 using 语句中实例化一个类将不再可能。但是除此之外,还有什么使 IDisposable 特别,特别是在添加接口的情况下?

3个回答

7
当你实现IDisposable接口时,基本上是添加了“当你使用完之后应该进行清理”的语义。这意味着,那些没有执行清理的现有代码违反了这个隐含的契约......因此,除非你也可以访问所有客户端代码,否则从“不实现IDisposable”到“实现IDisposable”是无法确保客户端使用你的类型正确的。
再举一个例子——假设GetHashCode的文档更新为“这应该始终返回偶数” - 这将是一个破坏性的变化,因为现有代码可能会返回奇数。同样,它不会在源代码中引起错误——一切都仍然可以编译——但你将改变契约以使其更加严格。
如果您将IDisposable的实现视为对客户端代码的契约,希望这能更清楚地说明为什么这是一个破坏性的变化。

当然。是的,你导致客户端代码不再正确使用该类。出于某种原因,我认为这会改变某种底层编译器机制(例如 using)。你的解释非常合理。 - Jonathon Reinhart

1

实现 IDisposable 告诉客户端他们总是需要处理该类。

这是一个破坏性的变更。


是的,现在很明显了 :-) 这会“破坏”那些从未编写Dispose()的客户端。 - Jonathon Reinhart

1
如果一个类没有实现IDisposable,使用该类的代码很可能会创建并且放弃实例而不检查它们是否实现了IDisposable。即使类的后续版本实现了IDisposable,也不会自动导致从未检查IDisposable的代码自动开始调用IDisposable.Dispose;只有客户端代码明确检查IDisposable并尝试在可能的情况下调用Dispose(例如与foreach块相关联的编译器生成的代码)才会在可能的情况下开始调用IDisposable.Dispose,但大多数代码不会这样做。
请注意,并非所有使用对象的代码都需要担心其实现IDisposable。唯一应该在对象上调用IDisposable的时间是,如果它是最后一个持有“有用”引用的东西。如果代码给方法一个对象的引用,并且该方法在返回后不会对该对象执行任何操作,则调用者将很容易知道方法何时完成了对该对象的操作,因此调用者将能够像方法一样调用Dispose。因此,方法没有理由调用Dispose。另一方面,如果代码调用对象的IEnumerable<T>.GetEnumerator实现,并且该方法需要清理枚举完成后将需要清理的对象的服务,则实现GetEnumerator的对象可能仅当调用GetEnumerator的代码在不再需要枚举器时在返回的枚举器上调用Dispose时才能知道服务不再需要。
顺便提一下,在像GetEnumerator这样的工厂方法返回的对象上是否需要调用IDisposable.Dispose并不一定取决于工厂方法的返回类型。如果在对象上调用非泛型的IEnumerable.GetEnumerator(),则语义正确性要求检查返回的对象实例是否实现了IDisposable(即使声明类型IEnumerable.GetEnumerator()没有实现),如果是,则调用其上的Dispose。从这个角度来看,当较早版本未这样做时,较晚版本的抽象基类实现IDisposable可能并不真正是一个破坏性的改变。如果要求调用返回抽象类的工厂方法的代码在可能的情况下调用Disposed,则让基类实现IDisposable不会为客户端代码增加任何新的责任——它只会使客户端代码更容易实际履行本来就应该履行的责任(检查对象实例是否实现IDisposable比在实现它作为一个无效方法的类上无条件地调用IDisposable.Dispose更慢、更繁琐)。

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