如何解释不同的实施需求?

时间:2013-05-14 01:59:08

标签: c#

假设我有一个带有两个具体类的接口。一个具体需要实现IDisposable。是否应修改接口以实现IDisposable以获得一个类的好处,或者接口的使用者是否必须对可处置性执行运行时检查?

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

2 个答案:

答案 0 :(得分:1)

如果框架本身是任何指示,那么使用接口实现IDisposable的适当性取决于可处置性是否是履行接口定义的合同的必要属性。少量Framework接口实现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: Do not raise exceptions in unexpected locations。)如果实现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

答案 1 :(得分:1)

我在阅读Mark Seemann关于依赖注入的书时找到了答案。接口上的IDisposable自动是一个漏洞抽象,因为IDisposable只是一个实现细节。也就是说,并非所有接口都是抽象,因此 - 严格按照接口编程的名称 - 会出现接口必须实现IDisposable的情况。

尽管比接口更具有可能的具体工具ID,但在这两种情况下,解决方案都是在资源上创建粗粒度抽象。然后抽象的每个方法实现都将创建和处理资源,从而减轻消费者做同样的负担。我喜欢这种方法,因为它降低了消费者生命周期管理的复杂性(实际上应该没有,特别是在DI中)。

为了在上面的场景中实现DI,你可能需要注入一个工厂,允许每个方法ad-hoc实例化依赖。