如果依赖容器或数据访问工厂可以返回可能实现IDisposable
的类型,那么客户是否有责任检查并处理它?在下面的代码中,一个数据类实现IDisposable
而另一个数据类不实现。数据访问工厂可以返回任何一个。
private static void TestPolymorphismWithDisposable()
{
// Here we don't know if we're getting a type that implements IDisposable, so
// if we just do this, then we won't be disposing of our instance.
DataAccessFactory.Resolve().Invoke();
// Do we just have to do something like this?
IFoo foo = DataAccessFactory.Resolve();
foo.Invoke();
var fooAsDisposable = foo as IDisposable;
if (fooAsDisposable != null) { fooAsDisposable.Dispose(); }
}
我问的原因是,这似乎给客户端代码带来了必须处理这个问题的负担,以及在构建API时,如何让客户知道他们可能需要呼叫处理?有没有更好的方法来处理这个问题,客户端不必检查IDisposable
?
为了完整的工作示例,以下是其他类:
public interface IFoo
{
void Invoke();
}
public class DataAccessBaseNotDisposable : IFoo
{
public void Invoke()
{
Console.WriteLine("In Invoke() in DataAccessBaseNotDisposable.");
}
}
public class DataAccessBaseDisposable : IFoo, IDisposable
{
public void Invoke()
{
Console.WriteLine("Invoke() in DataAccessBaseDisposable.");
}
public void Dispose()
{
Console.WriteLine("In Dispose() in DataAccessBaseDisposable.");
}
}
public static class DataAccessFactory
{
public static IFoo Resolve()
{
return new DataAccessBaseDisposable();
//return new DataAccessBaseNotDisposable();
}
}
答案 0 :(得分:3)
编辑:最好的解决方案是始终返回IDisposable对象,即使该对象不需要处理。这样,框架用户不必一直检查IDisposable。
您的框架将如下所示:
public interface IFoo : IDisposable
{
void Invoke();
}
public class DataAccessBase : IFoo
{
public void Invoke()
{
Console.WriteLine("In Invoke() in DataAccessBase.");
}
public void Dispose()
{
Console.WriteLine("In Dispose() in DataAccessBase.");
}
}
public static class DataAccessFactory
{
public static IFoo Resolve()
{
return new DataAccessBase();
}
}
它按照你的预期消耗:
private static void TestPolymorphismWithDisposable()
{
using(IFoo foo = DataAccessFactory.Resolve())
{
foo.Invoke();
}
}
然而,如果您是一名用户并且您遇到可能会或可能不会实现IDisposable的结果,则需要按以下方式使用它:
private static void TestPolymorphismWithDisposable()
{
IFoo foo = DataAccessFactory.Resolve();
using(foo as IDisposable)
{
foo.Invoke(); //This is always executed regardless of whether IDisposable is implemented
//Dispose is called if IDisposable was implemented
}
}
请参阅以下问题:Using statement with a null object和Using 'as IDisposable' in a using block
答案 1 :(得分:2)
我认为让IFoo继承IDisposable是可行的方法。唯一的例外可能是,如果有一定数量的IFoo实现,框架作者拥有它们(也就是说,它们不能或不打算由其他人提供),并且它们都不是一次性的。
答案 2 :(得分:2)
与IEnumerator
(在.NET 1.0中首次定义)与IEnumerator<T>
(首先在.NET 2.0中定义)的不同之处进行比较。
对于前者,foreach
喜欢:
foreach(string s in SomeStringSource)
{
Console.WriteLine(s);
}
被视为:
var en = SomeStringSource.GetEnumerator();
try
{
while(en.MoveNext())
{
string s = (string)en.Current;
Console.WriteLine(s);
}
}
finally
{
if(en is IDisposable)
((IDisposable)en).Dispose();
}
(当然不是var
,因为它是.NET 1.0,但是涉及类似的编译时类型推理。)
对于后者,因为IEnumerable<T>
被定义为继承自IDisposable
,所以相同的代码被视为:
using(var en = SomeStringSource.GetEnumerator())
while(en.MoveNext())
{
string s = (string)en.Current;
Console.WriteLine(s);
}
现在,foreach
大部分时间都隐藏了这一点,但是:
IEnumerator<T>
的情况下,第二种方法可以更好地处理。因此,通过类比,我们可以认为让IFoo
继承IDisposable
,因此总是有Dispose()
实现(尽管可能是空的),这是由经验支持的导致.NET团队从1.0-> 2.0开始进行更改,以及许多用户习惯的方法(IEnumerator<T>
毕竟是最常用的类型之一)。此外,FxCop等代码分析工具可以捕获失败处理。
答案 3 :(得分:1)
此类问题的回答不应基于用户的方便,而应基于所涉及概念的含义。
在您的情况下,您正在询问涉及数据访问的方案。从概念上讲,可以合理地预期数据访问类将需要处理非托管资源,例如数据库连接,文件句柄等。因此,假设的IFoo
应该继承自IDisposable
,因为这样做很有意义它的性质决定了它的作用,不是,因为它对用户来说很方便。
如果我们将此主题扩展到一个通用的依赖项注入或工厂系统,即一个创建执行各种任务的对象的系统,那么所有接口都从IDisposable
继承就没有任何意义,因为他们可以拥有它。这是一个巨大的反模式。
依赖项注入框架完成此工作的方式是维护一个“范围”,该范围跟踪它提供的所有一次性服务,以便在放置示波器时,它可以遍历一次性用品集合并进行处理。现在,这将是正确的方法。
这使我们回到您的情况。目前尚不清楚如何确定返回IFoo
的实现,但是放弃该方法并立即使用依赖项注入框架可能会很有用,然后再走错方向并挖出一个深坑尝试重塑一个。