假设我有一个包含两个具体类的接口。一具体需要落实IDisposable
。是否应该修改接口以实现IDisposable
一个类的利益,或者接口的使用者是否必须执行运行时检查以确保可处置性?
我认为应该修改接口,因为它是一个简单的更改(特别是如果它是一个新接口),但我也可以看到在更改设计以适应特定实现时可能违反 liskov(特别是如果其他类或类必须抛出不支持的异常)
假设我有一个包含两个具体类的接口。一具体需要落实IDisposable
。是否应该修改接口以实现IDisposable
一个类的利益,或者接口的使用者是否必须执行运行时检查以确保可处置性?
我认为应该修改接口,因为它是一个简单的更改(特别是如果它是一个新接口),但我也可以看到在更改设计以适应特定实现时可能违反 liskov(特别是如果其他类或类必须抛出不支持的异常)
如果框架本身有任何迹象,那么实现接口的适当性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
实现IDbDataAdapter
和IDisposable
;完全有可能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
。
我在阅读 Mark Seemann 关于依赖注入的书时找到了答案。接口上的 IDisposable 自动成为泄漏抽象,因为 IDisposable 仅是实现细节。也就是说,并非所有接口都是抽象的,因此——以严格针对接口编程的名义——将存在接口必须实现 IDisposable 的情况。
尽管具体实现 IDisposable 比接口更可取,但在这两种情况下,解决方案都是在资源上创建粗粒度抽象。然后,抽象的每个方法实现都会创建和处置资源,从而减轻消费者的负担。我喜欢这种方法,因为它降低了消费者生命周期管理的复杂性(这真的不应该,尤其是在 DI 中。)
为了在上述场景中实现 DI,您可能需要注入一个工厂,允许每个方法临时实例化依赖项。