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

时间:2013-08-12 17:06:45

标签: .net idisposable

IDisposable的MSDN文档指出:

  

IDisposable接口添加到现有类是一个版本更改,因为它会更改类的语义。

完全这是什么意思?

我可以看到删除 IDisposable是多么重要。例如,在using语句中实例化一个类将不再可能。但是还有什么让IDisposable变得特别,特别是在添加界面的背景下?

3 个答案:

答案 0 :(得分:7)

当你实现IDisposable时,你基本上会添加“当你完成它时应该处理它”的语义。这意味着没有处理它的现有代码违反了该隐式契约......所以没有办法从“不实现IDisposable”转变为“实施{{1} “同时保持客户端正确使用您的类型,除非您还可以访问所有客户端代码。

再举一个例子 - 假设IDisposable的文档更新为“这应该总是返回一个偶数” - 这将是一个重大变化,因为现有代码很容易返回一个奇数。同样,它不会破坏源代码 - 一切仍然会构建 - 但你要改变合同以使其更具限制性。

如果您将GetHashCode的实施视为客户端代码的合同,那么希望它更清楚为什么会发生重大变化。

答案 1 :(得分:1)

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

这是一个重大改变。

答案 2 :(得分:1)

如果某个类没有实现IDisposable,那么使用该类的代码可能会创建并放弃实例而不检查它们以查看它们是否实现IDisposable。即使该类的更高版本确实实现了IDisposable,该更改也不会神奇地导致从不检查IDisposable的代码神奇地开始调用IDisposable.Dispose;客户端代码显式检查IDisposable并尝试在可能的情况下调用Dispose(例如,与foreach块关联的编译器生成的代码)将在可能的情况下开始调用IDisposable.Dispose ,但大多数代码都不会打扰。

请注意,并非所有使用对象的代码都必须担心其实现IDisposable。某事物应该在一个对象上调用IDisposable的唯一时间是它是最后一个持有“有用”引用的东西。如果代码给一个方法一个对象的引用,并且该方法在返回后不会对该对象做任何事情,那么调用者将能够很容易地知道该方法何时完成该对象,因此调用者将同样能够调用Dispose作为方法。因此,该方法没有理由调用Dispose。另一方面,如果代码调用对象的IEnumerable<T>.GetEnumerator实现,并且该方法需要一个枚举完成后需要清理的对象的服务,那么实现GetEnumerator的对象的唯一方法就是如果在不再需要的情况下调用GetEnumerator的代码在返回的枚举器上调用Dispose,则不再需要这些服务。

顺便说一句,是否有必要对IDisposable.Dispose等工厂方法返回的对象调用GetEnumerator并不一定取决于工厂方法的返回类型。如果在对象上调用非泛型IEnumerable.GetEnumerator(),则语义正确性要求检查返回的对象实例是否实现IDisposable(它可能会这样做,即使声明的IEnumerable.GetEnumerator()类型不这样做)如果是这样的话就打电话给Dispose。从这个角度来看,如果早期版本没有这样做,那么拥有更高版本的抽象基类实现IDisposable可能并不是真正的重大改变。如果调用返回抽象类的工厂方法的代码在可能的情况下需要在返回的项上调用Disposed,那么使用基类实现IDisposable将不会向客户端添加任何新的职责代码 - 它只会使客户端代码更容易实际关注它将要承担的职责(检查对象实例实现IDisposable是否比调用IDisposable.Dispose更慢,更麻烦无条件地在一个实现它作为无用方法的类上。)