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

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

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

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

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

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

相比之下,在框架中非常常见,具体类直接实现IDisposable (而不是通过另一个接口)。 对于框架类,可以通过reflection查询:

 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()不应该抛出exception(参见CA1065:不要在意外位置引发exception 。)如果实现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关于dependency injection的书时找到了答案。 接口上的IDisposable自动是一个漏洞抽象,因为IDisposable只是一个实现细节。 也就是说,并非所有接口都是抽象,因此 – 严格按照接口编程的名称 – 会出现接口必须实现IDisposable的情况。

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

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