3

假设我有一个包含两个具体类的接口。一具体需要落实IDisposable。是否应该修改接口以实现IDisposable一个类的利益,或者接口的使用者是否必须执行运行时检查以确保可处置性?

我认为应该修改接口,因为它是一个简单的更改(特别是如果它是一个新接口),但我也可以看到在更改设计以适应特定实现时可能违反 liskov(特别是如果其他类或类必须抛出不支持的异常)

4

2 回答 2

1

如果框架本身有任何迹象,那么实现接口的适当性IDisposable取决于可处置性是否是履行接口定义的合同的必要属性。少数框架接口确实实现了IDisposable,包括:

System.Collections.Generic.IEnumerator<T>
System.Deployment.Internal.Isolation.Store
System.Resources.IResourceReader
System.Resources.IResourceWriter
System.Security.Cryptography.ICryptoTransform
System.ComponentModel.IComponent
System.ComponentModel.IContainer

就其性质而言,这些接口通常定义将消耗并因此需要释放资源的构造。从这个意义上说,资源的处置可以被认为是实现契约的一个组成部分,而不是实现接口的具体类的实现细节。例如,IResourceReader遗嘱从资源中读取,关闭资源是实现合同的必要部分。

相反,在框架中IDisposable直接实现具体类(而不是通过另一个接口)的情况很常见。对于框架类,可以通过反射来查询:

foreach (var v in typeof(/*any type*/)
                      .Assembly.GetTypes()
                      .Where(a => a.IsClass 
                              && typeof(IDisposable).IsAssignableFrom(a)
                              && a.GetInterfaces().Where(
                               i=>i!=typeof(IDisposable)
                       ).All(i=>!typeof(IDisposable).IsAssignableFrom(i))))
{
   foreach (var s in v.GetInterfaces())
       Console.WriteLine(v.FullName + ":" + s.Name);
}

通常,这些是其实现需要消耗资源的类,附带履行接口契约。例如,分别System.Data.SqlClient.SqlDataAdapter实现IDbDataAdapterIDisposable;完全有可能IDbDataAdapter不需要配置,但实现SqlDataAdapter需要消耗和释放资源。

在您的情况下,您指出有两个类实现了您的接口,一个需要实现IDisposable,一个不需要实现。鉴于没有,根据定义,处理资源的能力不是满足接口要求的必要条件;因此接口本身不应该实现IDisposable

顺便说一句,Dispose()不应该抛出异常(参见CA1065:不要在意外位置引发异常。)如果实现的类实例IDisposable没有资源可处置,它可以简单地返回;满足释放所有资源的后置条件。没有必要抛出NotSupportedException.

附录

第二个潜在的考虑因素是接口的预期用途。例如,在数据库场景中通常使用以下模式:

 System.Data.IDbCommand cmd = ...;
 using (var rdr = cmd.ExecuteReader()) // returns IDataReader (IDisposable)
 {
     while (rdr.Read()) {...}
 } // dispose

如果IDataReader不实现IDisposable,则等效代码将需要复杂得多:

 System.Data.IDbCommand cmd = ...;
 System.Data.IDataReader rdr;
 try
 {
     rdr = cmd.ExecuteReader();
     while (rdr.Read()) {...};
 } finally {
     if (rdr is IDisposable) ((IDisposable)rdr).Dispose();
 }

如果预计这种类型的用法很常见,那么IDisposable即使并非所有实现都有望实现,也可以证明将接口作为一种特殊情况是合理的IDisposable

于 2013-05-14T03:54:07.893 回答
1

我在阅读 Mark Seemann 关于依赖注入的书时找到了答案。接口上的 IDisposable 自动成为泄漏抽象,因为 IDisposable 仅是实现细节。也就是说,并非所有接口都是抽象的,因此——以严格针对接口编程的名义——将存在接口必须实现 IDisposable 的情况。

尽管具体实现 IDisposable 比接口更可取,但在这两种情况下,解决方案都是在资源上创建粗粒度抽象。然后,抽象的每个方法实现都会创建和处置资源,从而减轻消费者的负担。我喜欢这种方法,因为它降低了消费者生命周期管理的复杂性(这真的不应该,尤其是在 DI 中。)

为了在上述场景中实现 DI,您可能需要注入一个工厂,允许每个方法临时实例化依赖项。

于 2013-05-18T22:27:42.243 回答