【问题标题】:Polymorphism when concrete types *might* be disposable当具体类型*可能*是一次性的时的多态性
【发布时间】:2014-01-06 15:59:45
【问题描述】:

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

【问题讨论】:

  • 我记得几个月前有一个类似的问题。据我记得你可以做using(SomeObjectThatMightOrMightNotBeDisposable as IDisposable) { }。这样你就不必明确检查它是否实现了IDisposable
  • 为什么不总是使用 IDisposable?让 IFoo 成为 IDisposable,即使 Dispose 不会为某些实现者做任何需要的事情。
  • @JeroenVannevel 我喜欢这样,但如果类型不是一次性的,当我们尝试调用它的方法时,我们的实例不会是 null 吗?
  • @Ralf 这是一个有趣的想法。这就像空对象模式。我可以有什么都不做的一次性方法,所以所有类型都可以用同样的方式处理。我得考虑一下。谢谢。
  • @BobHorn 这就是 Streams 的工作方式。所有 Streams 都是 IDisposable 的,即使是不需要它的 MemoryStream。

标签: c# polymorphism idisposable


【解决方案1】:

编辑:最好的解决方案是始终返回一个 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

【讨论】:

  • 我否决了这个答案。是的,它很聪明,但我认为它只是展示了如何不这样做。 Resolve 方法没有显示它可能是 IDisposable。框架用户是否应该用这样的代码包围来自该框架的任何方法的任何返回对象,只是因为它也可能是 IDisposable,即使返回的类型没有这么说?
  • 公平点,我想这个答案假设你是一个用户,并且你被一个以这种方式实现它的框架所困。
  • @Ralf 为什么不在 IFoo 继承 IDisposable 的地方添加一个答案?
  • @Ralf 说得好。框架用户应该如何知道甚至使用这种方法?通过一些 API 文档?也许最好的方法是让所有具体类型都实现 IDisposable,即使没有必要。
  • 我赞成这个答案,因为除非返回的合同是从 IDisposable 本身继承的,否则调用者无法知道它,除非通过文档这不是很好的方法。 IFoo 需要从 IDisposable 继承,或者调用者需要测试 IDisposable。
【解决方案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. 第二种方法更容易实现;提醒您,处置通常是枚举器的问题,因此您不太可能忘记它(我看到不止一个旧的前泛型枚举器在应有的时候未能处置)。

因此,通过类比,我们可以认为,IFoo 继承自 IDisposable,因此始终具有 Dispose() 实现(尽管可能为空),这得到了导致 .NET 的经验的支持团队从 1.0->2.0 进行更改,同时也是许多用户习惯的一种方法(IEnumerator<T> 毕竟是最常用的类型之一)。此外,FxCop 等代码分析工具可能会发现未处理的问题。

【讨论】:

  • 所以你是说IFoo 应该实现IDisposable,对吧?
  • 是的,或者更确切地说从它继承。最后一段是我将类比带回到问题的地方。
【解决方案3】:

我认为让 IFoo 继承 IDisposable 是可行的方法。唯一的例外可能是如果有固定数量的 IFoo 实现,框架作者拥有它们(也就是说,它们不能或不打算由其他人提供),并且它们都不是一次性的。

【讨论】:

  • 50,000 美元的问题应该是,工厂方法是否会返回一个除了实现接口之外一无所知的对象的合理理由。 IEnumerator<T> 绝对是这样(其存在的主要原因是作为IEnumerable<T> 的返回类型),但不适用于许多其他人。
【解决方案4】:

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

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

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

依赖注入框架实现这一点的方式是通过维护一个“范围”来跟踪它提供的所有一次性服务,以便在释放范围时,它可以遍历一次性集合并也释放它们。现在这将是正确的方法。

这让我们回到您的情况。目前尚不清楚您如何决定返回 IFoo 的哪个实现,但放弃这种方法并立即使用依赖注入框架可能会很有用,在您走错方向并为自己挖一个深洞试图重新发明一个之前.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-14
    • 1970-01-01
    • 2013-12-23
    • 2013-05-06
    相关资源
    最近更新 更多