设计一个只有某些实现需要IDisposable的接口

本文关键字:实现 IDisposable 接口 一个 | 更新日期: 2023-09-27 18:06:56

您正在设计一个接口IFoo

public interface IFoo
{
   void Bar();
}

假设这个接口有五种实现。其中两个实现还应该实现IDisposable,因为它们使用非托管资源。从调用者的角度来看,如果IFoo实现了IDisposable,那么任何IFoo都可以封装在using块中,这将是最简单的,但当然,一些实现会充斥着空的Dispose()方法。只是好奇有其他方法可以做到这一点吗?

设计一个只有某些实现需要IDisposable的接口

我怀疑我只是要求IDisposable()——无操作Dispose()并不是很大的开销。

如果你不能确定它是否是一次性的,下面的方法非常有效:

var mightBeDisposable = GetBlah();
using(mightBeDisposable as IDisposable)
{
   // etc
}

.NET Framework中有实现IDisposable的接口的先例,例如IComponentIDataReader

当您期望大多数实现都需要处置时,这似乎是一种合理的模式。

尽管实现接口的对象通常承诺在没有这些接口的对象中不常见的能力或特性,并且尽管IDisposable是一个接口,但对象实现IDisposable的事实并没有承诺在没有接口的对象中将缺乏的任何能力或特性。相反,对象实现IDisposable的事实意味着它缺乏在不实现它的对象中常见的特征:任何获得或持有引用的人都可以放弃它,而不考虑对象是否可以或应该首先清理。

如果使用特定接口类型的代码通常不是最后一个保存引用的代码,那么接口就不需要实现IDisposable。即使不能安全地放弃某些实现,对于除了最后一个拥有引用的实例之外的实例的任何用户来说,这都无关紧要。如果用户通常会比接口所暗示的更多地了解特定类型,那么无论接口是否指示,用户都会知道对象是否需要清理

另一方面,如果一个对象的最后一个用户除了实现某个接口之外,通常对它一无所知,那么即使只有一小部分实现需要清理,该接口也应该继承IDisposable(也许尤其如此!(。考虑IEnumerableIEnumerable<T>的情况。任何调用IEnumerable<T>.GetEnumerator()的代码都将接收可能是宇宙中任何地方对实现IDisposable的对象的唯一引用。因此,该代码负责确保对该引用调用Dispose。任何调用IEnumerable<T>.GetEnumerator()、既不调用返回值的Dispose,也不将其提供给其他承诺这样做的代码的代码都将被破坏。

非泛型IEnumerable.GetEnumerator返回的类型未实现IDisposable。事实上,这似乎表明调用IEnumerable.GetEnumerator的代码没有责任处理它。不幸的是,这种暗示是不正确的。调用IEnumerable.GetEnumerator的代码有责任确保,如果返回的特定实例实现IDisposable,则必须调用其Dispose方法。不承担该责任的代码与未能处理IEnumerable<T>.GetEnumerator返回的代码一样,都是被破坏的。IEnumerable.GetEnumerator返回类型(即IEnumerator(未能实现IDisposable并不能消除调用方清理返回对象的责任。这只会使这种责任更加繁重,并增加代码无法做到这一点的可能性。

我不会强迫IFoo实现IDisposable,因为它是针对SOLID的。如果你愿意,你可以从IFoo派生IDisposableFoo,或者如果你需要,你可以进行检查(甚至是将IFoo封装到DisposableAdapter中并检查IDisposable的自定义方法(。

class DisposableAdapter : IDisposable, IFoo
{
   IFoo _obj;
   public DisposableAdapter(IFoo obj)
   {
      _obj = obj;
   }
   public void Dispose()
   {
      if (_obj is IDisposable)
        ((IDisposable)obj).Dispose();
   }     
   // copy IFoos implementations from obj
}

使用

using(var foo = new DisposableAdapter(myFoo)) //... use foo just as you had myFoo

如果您不介意在调用站点中添加一些代码,您可以执行以下操作:

IFoo foo = GetMeSomeFoo();
foo.UseFoo();
var disposableFoo = foo as IDisposable;
if (disposableFoo != null)
    disposableFoo.Dispose();

不漂亮,但不会污染你的界面。也不能确保打电话的人会做所有这些事情。

编辑:正如Hans Passant所指出的,它本质上等于

IFoo foo = GetMeSomeFoo();
using (foo as IDisposable) {
    foo.UseFoo();
} 

您可以创建一个一次性版本的界面,更好地显示您的意图:

public interface IDisposableFoo : IFoo, IDisposable
{
}

任何继承此接口的类仍然可以被视为IFoo。您可能遇到的一个问题是,在处理IFoo对象之前,需要检查它是否是一次性版本,但这相当容易。

您要么需要两个接口,一个实现IDisposable,另一个不实现IFooIDisposableFoo,要么只使IFoo可丢弃。

IDisposable的一些空实现没有害处。考虑到您已经有一些需要处理的内容,这似乎是一个很可能的用例。