【问题标题】:IDisposable implementation of a non-IDisposable interface非 IDisposable 接口的 IDisposable 实现
【发布时间】:2011-05-04 21:38:10
【问题描述】:

接口不继承自 IDisposable 时,正确处理接口的默认实现的最佳方法是什么?例如,假设我想做

public class FooGetter : IDisposable {

    private IFooProvider fooProvider = MyContainer.GetDefault<IFooProvider>();
    ...
    public void Dispose(){
         ...
         if (fooProvider != null) fooProvider.Dispose(); // obviously has compile error here
    }
}

恰好IFooProvider的默认实现是IDisposable,但IFooProvider接口并没有继承自IDisposable。我应该如何/在哪里处理它?

问题不仅仅在于依赖注入容器;它也适用于紧密耦合的依赖:

private IFooProvider fooProvider = new PatrickProvider();

在这种情况下,我可以保留另一个引用,以便以后可以 Dispose() 它,但这似乎真的很麻烦:

private PatrickProvider defaultFooProvider = new PatrickProvider();
private IFooProvider fooProvider = defaultFooProvider;

在这里寻找最佳(或良好)做法。

【问题讨论】:

  • 你在控制 IFooProvider 吗?帕特里克提供者?您是在寻找设计它们的最佳做法,还是仅在现有的情况下使用它们?
  • 按原样使用它们——假设 IFooProvider 没有实现 IDisposable,但 PatrickProvider 实现了。
  • 这根本不是真的。具体实现的new-er 应该负责处理,因为new-er(例如DI容器)知道如何将IFooProvider映射到具体实现,并且还具有特定于该特定的所有代码实施。
  • 这些是我的想法,是的:)。有时您还需要通知容器您已完成对象的能力;大多数 DI 容器都提供此功能(通常名称为 Release)。但是仍然由容器来决定是否该 dispose 了,例如如果一个对象具有单例生命周期,那么它将等待所有消费者释放后再释放。

标签: .net oop idisposable


【解决方案1】:

使用 DI 容器的最佳做法是让 DI 容器为您处理对象的生命周期。如果您将 DI 容器用作服务定位器(嘘!反模式!!),这是不可能的,就像您似乎对 MyContainer.GetDefault 所做的那样,但是如果您通过构造函数注入正确地注入了依赖项,那么它会工作得很好。

对于紧密耦合的依赖,你必须做一些愚蠢的事情,比如

using(fooProvider as IDisposable)
{
    // Code that uses fooProvider
}

我认为(但不是 100% 肯定)using 如果通过了null 将什么都不做,所以无论fooProvider 是否实现IDisposable,这都会起作用。

在 Mark Seemann 的 Dependency Injection in .NET 第 8 章中有一个很好的讨论 IDisposable,因为它与依赖项和 DI 相关。

【讨论】:

  • 你是对的 - 当给定 null 时,using 块不会做任何事情。
  • 我将其标记为正确,但这些答案中的讨论非常有帮助。
【解决方案2】:

您可以在运行时检查该对象是否实现了IDisposable,如果是,则将其丢弃:

public class FooGetter : IDisposable
{
    private IFooProvider fooProvider = MyContainer.GetDefault<IFooProvider>();
    ...
    public void Dispose()
    {
         IDisposable disposable = fooProvider as IDisposable;

         if (disposable != null)
         {
             fooProvider.Dispose();
         }
    }
}

您仍然需要知道接口的实现可能是一次性的,否则代码有点毫无意义,但对于您的情况,它将确保在具有它的实现上调用 Dispose()

理想情况下,您的界面应该从IDisposable 派生,以声明其合同的一部分是您必须在完成后处理它,但我承认实际上这可能并不总是可能的。

【讨论】:

  • “理想情况下,你的接口应该从 IDisposable 派生”——这是我想要避免的,因为一个类不需要从 IDisposable 继承来提供 Foo。 druttka 有我的想法。
  • @Patrick:正如我所说,“......我承认实际上这可能并不总是可能的”。
【解决方案3】:

在我看来,有几件事需要考虑。

首先,想象一个接口 IFoo 是完全合理的,其中一些实现可能有资源可以处理或清理来执行,而其他实现可能没有;因此,有些人认为不让 IFoo 继承 IDisposable 是合理的。

public interface IFoo{
    // ...
}

// Has no cleanup or resources
public interface Foo1:IFoo{
    // ...
}

// Does have resources to release and cleanup to perform
public interface Foo2:IFoo,IDisposable{
    // ...
}

请参阅this answerthis discussion,了解实现 IDisposable 的选择应该取决于具体类还是抽象类/接口。 编辑: 打动了本段的大部分内容,因为在回顾了讨论之后,我看到了“让接口定义它”方面的优点,并将重新评估我的意见。此外,该决定与所提出的问题并不真正相关。

除此之外,如果您有 IFoo x,并且某些实现实现了 IDisposable 而其他没有实现,您可以检查 x 是否实现 IDisposable。如果确实如此,请妥善处理。如果不是,那么您应该相信编写 x 的 IFooProvider 实现的人没有实现 IDisposable,因为您不需要调用它,并且知道您已尽您所能确保安全。

IFoo x; 
//x = whatever
if (x is IDisposable)
{
    ((IDisposable)x).Dispose();
} 

【讨论】:

  • 我不喜欢后面的代码。您的第二个链接讨论的帖子#4 说得最好。如果工厂方法或构造函数的返回类型实现了 IDisposable,则会创建一个强推论,调用者需要确保释放创建的对象。相反,如果工厂方法或构造函数的返回类型没有实现 IDisposable,就会产生一个可以安全地创建和放弃它的强大推断
  • 我完全同意这一点,这也是我今天比昨天更倾向于在抽象上定义它的部分原因。但是,如果您无法控制现有接口和实现,但碰巧知道某些但不是所有实现也实现 IDisposable,则需要进行某种类型检查?
  • 另外我完全同意的原因是因为它说的是“强推论”而不是“保证”
  • @supercat 响铃的目的是在运行时提醒人们注意情况。危险在于只依赖钟声而忽视眼前的事物。用你的例子,当你应该处理的时候没有处理就像被火车撞了一样。我们同意返回 IDisposable 的方法是您应该的强推论(铃)。在某些情况下(Patrick 有一个,我相信),返回的接口不会继承 IDisposable,但该接口的某些实现会继承(响铃已失败)。需要根据自己的判断采取适当的行动。
  • @druttka: ...即使 Dispose 方法什么都不做,也可能会排除这种优化)。太糟糕了,微软似乎将 IDisposable 设计为事后才想到的,因为我们现在不幸地坚持了设计中的一些弱点(例如,如果在处理异常期间调用 Dispose,并且 Dispose 本身因异常而失败,最好的行为可能是 Dispose 将这两个异常都包装在 CleanupException 中,但 Dispose 没有执行此操作所需的信息。
【解决方案4】:

如果您要定义 IFooProvider,为什么不让它继承 IDispoable?如果有人想出一个不需要清理的实现,它可以简单地实现一个空的 Dispose 方法。

【讨论】:

  • 因为这违反了关注点分离——IFooProvider 负责指定一个类必须是什么才能提供 foo。一次性用品并不总是必需的。
  • @Patrick Szalapski:IDisposable 接口在某种意义上是落后的,因为更强的承诺适用于实现它的类。 IDisposable 接口并不意味着一个类实际上需要清理,正如缺少 IDisposable 接口意味着它不需要清理一样。 IDisposable 承诺做所有必要的清理工作;如果那个“一切”什么都不是,它就履行了它的义务。如果工厂方法可能返回调用者应该处理的对象,则返回类型应该实现 IDisposable。
  • @Patrick Szalapski:如果期望将接口用作产生需要处置的对象的工厂方法的返回类型,那么它应该实现 IDisposable。这种最常见的情况是工厂本身是一个接口时(例如,如果 IFactory 应该有一个返回 ICar 的 MakeCar 函数,并且某些 MakeCar 函数将返回一个 IDisposable 对象,那么 ICar 应该实现 IDisposable)。
  • @Patrick IDisposable 仅应在您希望传达处置是接口合同的一部分时应用于接口。这对我来说似乎非常罕见,因为处置(几乎?)总是一个实现细节。我能想到的唯一有意义的情况是,当您尝试使用 using 作为成语做一些聪明的事情时,例如正如在 ASP.NET MVC 中使用 using(Html.BeginForm()) 所做的那样。
  • @supercat:不过,我不认为ITileRenderer 应该是一次性的!可处置性是一个实现细节,而不是作为瓦片渲染器合同的一部分。如果ITileRenderer 的用户需要显式释放一个对象,它应该通过一个更容易混淆的机制让工厂知道,比如TileRendererFactory.Release。然后TileRendererFactory 可以决定是否根本不处理,现在处理,当其他消费者完成时处理......此时TileRendererFactory 当然是一个 DI 容器,所以你可能应该使用其中之一。跨度>
猜你喜欢
  • 1970-01-01
  • 2018-12-30
  • 2012-02-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-30
  • 2011-07-31
  • 1970-01-01
相关资源
最近更新 更多