具体类型*可能是一次性的多态性

时间:2014-01-06 15:59:45

标签: c# polymorphism idisposable

如果依赖容器或数据访问工厂可以返回可能实现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();
    }
}

4 个答案:

答案 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 objectUsing '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大部分时间都隐藏了这一点,但是:

  1. 在您需要自己处理IEnumerator<T>的情况下,第二种方法可以更好地处理。
  2. 第二种方法更好实施;提醒您,处理通常是调查员的问题,因此您不太可能忘记它(我看到不止一个旧的前仿制药调查员在应该处理时未能处理)。
  3. 因此,通过类比,我们可以认为让IFoo继承IDisposable,因此总是有Dispose()实现(尽管可能是空的),这是由经验支持的导致.NET团队从1.0-> 2.0开始进行更改,以及许多用户习惯的方法(IEnumerator<T>毕竟是最常用的类型之一)。此外,FxCop等代码分析工具可以捕获失败处理。

答案 3 :(得分:1)

此类问题的回答不应基于用户的方便,而应基于所涉及概念的含义。

在您的情况下,您正在询问涉及数据访问的方案。从概念上讲,可以合理地预期数据访问类将需要处理非托管资源,例如数据库连接,文件句柄等。因此,假设的IFoo应该继承自IDisposable,因为这样做很有意义它的性质决定了它的作用,不是,因为它对用户来说很方便。

如果我们将此主题扩展到一个通用的依赖项注入或工厂系统,即一个创建执行各种任务的对象的系统,那么所有接口都从IDisposable继承就没有任何意义,因为他们可以拥有它。这是一个巨大的反模式。

依赖项注入框架完成此工作的方式是维护一个“范围”,该范围跟踪它提供的所有一次性服务,以便在放置示波器时,它可以遍历一次性用品集合并进行处理。现在,这将是正确的方法。

这使我们回到您的情况。目前尚不清楚如何确定返回IFoo的实现,但是放弃该方法并立即使用依赖项注入框架可能会很有用,然后再走错方向并挖出一个深坑尝试重塑一个。