【问题标题】:Autofac: How to limit the lifetime of an IDisposable object without passing around the IoC containerAutofac:如何在不传递 IoC 容器的情况下限制 IDisposable 对象的生命周期
【发布时间】:2010-02-13 13:04:58
【问题描述】:

我目前正在学习如何使用 Autofac,但我一直坚持以确定性方式处理 IDisposable 对象。在我陈述我的问题之前,让我先介绍一下情况。

起始位置:

假设我的对象模型是通过以下接口定义的:

interface IApple : IDisposable
{
    void Consume();
}

interface IHorse
{
    void Eat(IApple apple);   // is supposed to call apple.Consume()
}

interface IHorseKeeper
{
    void FeedHorse();   // is supposed to call horse.Eat(apple)
                        //   where 'horse' is injected into IHorseKeeper
                        //   and 'apple' is generated by IHorseKeeper on-the-fly
}

此外,我定义了一个将用作IApple 工厂的委托:

delegate IApple AppleFactory;

Autofac 配置:

现在,我将按如下方式注册上述类型 - 请注意,我省略了 AppleHorse 两个类的代码,因为它们实现起来很简单:

var builder = new Autofac.ContainerBuilder();

builder.RegisterType<Apple>().As<IApple>();
builder.RegisterType<Horse>().As<IHorse>();
builder.RegisterType<HorseKeeper>().As<IHorseKeeper>();
builder.RegisterGeneratedFactory<AppleFactory>();

我的问题:

我不太清楚如何实现方法IHorseKeeper.Feed。这是我目前拥有的:

class HorseKeeper : IHorseKeeper
{
    private readonly IHorse horse;
    private readonly AppleFactory appleFactory;

    public HorseKeeper(IHorse horse, AppleFactory appleFactory)
    //                 ^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^^^^^^^
    //                         constructor injection
    {
        this.horse = horse;
        this.appleFactory = appleFactory;
    }

    public void FeedHorse()
    {
        using (var apple = appleFactory())
        {
            horse.Eat(apple);
        }  // <- Dispose() apple now (ASAP), as it's no longer needed!
    }
}

这是我想要的那种代码,因为它完全与 Autofac 无关。只要AppleFactory 按预期工作,它也可以与另一个 IoC 容器一起工作。

但是,由于 Autofac 为我处理 AppleFactory,它会跟踪它为我生成的所有 IApple 对象,因此在容器的生命周期结束时会希望 Dispose 它们自己。即产生的apple会被处理两次。

我认为将 IApple 注册为 .ExternallyOwned() 不是可行的解决方案,因为在某些情况下,让 Autofac 处理 IApples 的生命周期可能更容易。

Deterministic disposal with Autofac 需要使用container.BeginLifetimeScope() 创建一个嵌套容器,但是我不想在HorseKeeper.FeedHorse 中使用它,因为那时HorseKeeper 变得依赖于 Autofac,我想保留我的代码 IoC - 不可知论者。

问题:

如何以与 IoC (Autofac) 无关的方式实现 HorseKeeper.FeedHorse,同时确保正确处理动态生成的对象?

【问题讨论】:

  • 我不能具体说明如何使用 Autofac 解决这个问题,但总的来说,让 IApple 从 IDisposable 派生是一个泄漏抽象。你最好重新考虑你的设计。
  • @Mark Seemann,我很想知道为什么IApple 也不需要实现类来实现IDisposable。一个简短的解释将不胜感激。
  • 为什么要这样?实现 IDisposable 向运行时以及其他开发人员表明所讨论的类型包含对非托管内存的引用。在接口的情况下,这根本是不可能的,因此它表明您正在让您对一个或多个具体实现的了解泄漏到抽象中。
  • 另外,看看 Nicholas Blumhardt 在 2 月 4 日凌晨 5:10 的讨论中的 cmets:cca.codeplex.com/Thread/View.aspx?ThreadId=82987 总结得很好。
  • 好的,谢谢。以后我会牢记这一点! (但是,我仍然不同意IDisposable 仅用于释放非托管资源。实施IDisposable IMO 还有其他正当理由。)

标签: c# inversion-of-control dispose idisposable autofac


【解决方案1】:

这里的其他答案很有见地,但是有问题。在这两种情况下,如果 Apple 有其他依赖项需要处理,则不会发生正确的清理。

Autofac 2 提供了一个新功能来提供帮助,称为“拥有的实例”。我注意到您的注册码是 Autofac 1.4,所以如果您无法升级,请告诉我(还有其他不太透明的方法可以做到这一点。)

像往常一样注册 Apple(非外部所有):

builder.RegisterType<Apple>().As<IApple>();

将 AppleFactory 声明为:

public delegate Owned<IApple> AppleFactory();

在 Autofac 2 中,您不再需要调用 RegisterGeneratedFactory() - 这是自动的。

然后,在 HorseKeeper 中,像这样喂马:

public void FeedHorse()
{
    using (var apple = appleFactory())
    {
        horse.Eat(apple.Value);
    }
}

(注意 .Value 属性以获取底层 IApple。

在 using 块结束时,苹果及其所有依赖项将被清理。

直接使用 IApple(作为依赖项)的任何其他组件都将获得通常的行为。

【讨论】:

  • +1 这很像实验性的 MEF PartCreator,它返回一个 PartLifetimeContext。 @stakx:“继承”ExternalOwned 不起作用 - 依赖关系的依赖关系何时以及由谁处理? Apple 不知道这些。
  • 啊,是的,真的很喜欢 Owned 的简洁
【解决方案2】:

唯一的方法是使用ExternallyOwned 修饰符来修改Apple 注册。这指示 Autofac 不跟踪要处置的对象,而是让外部人员(您的代码)处理处置。但正如您所说,您现在必须确保手动处理所有 Apple 实例,因为您不会从 Autofac 获得自动帮助。

builder.RegisterType<Apple>().As<IApple>().ExternallyOwned();

不过,通过此注册,您的 Feed 代码将按预期工作。

注意:关于接口是否应该继承IDisposable的讨论:IMO,当接口继承IDisposable时,这是对“消费”开发人员的指示,该实例应该在某个时间点被处置。在 IApple 的情况下,由于该接口也是 IDisposable,因此开发人员应确保处置实例(并且必须然后也注册为 ExternallyOwned)。另一方面,如果 Apple 类看起来像这样:

class Apple: IApple, IDisposable
{ }

IApple 的消费者现在完全不知道实例是 IDisposable 的。在这种情况下,我们将让容器处理处置。

所以我的结论是,作为 Apple 和 IApple 的开发者,我可以选择是要求消费者处理还是将其留在容器中。

【讨论】:

  • 感谢您提供如此清晰而有用的解释,尤其是关于 ExternallyOwned 如何与 IDisposable 很好地协同工作以实现一致的设计。我仍然有点失望,尽管这似乎是一个全有或全无的情况,即。似乎没有合理的方法告诉 Autofac 仅在某些情况下管理处置。
  • 不确定我是否清楚地理解“某些实例”的含义。 LifetimeScope 是一种对容器中的处理进行更细粒度控制的方法,但我可以看到这如何将容器暴露给其他不知道的类。否则,如果没有来自外部代码的任何输入,我无法看到 Autofac 如何知道何时应该处理实例。
  • 对于“某些实例”,我的意思基本上是@wcoenen 在他的回答中所展示的——即两次注册相同的具体类型,但针对不同的接口,其中一个标记为ExternallyOwned 和其他不是。然后一个类可以从这些版本中进行选择,从而在很大程度上与 IoC 无关(我认为)。
  • Owned 是您正在寻找的 Autofac 的线索。 ExternallyOwned 仅适用于 Apple,因此如果 Apple 有需要处理的依赖项,则不幸的是,此选项将“泄漏”。
【解决方案3】:

如果你有时想自己管理Apple实例的生命周期,有时让容器来处理,那么你可以定义两个接口:

public IApple
{
   void Consume();
}

public IDisposableApple : IApple, IDisposable
{
}

然后注册两次类:

builder.RegisterType<Apple>().As<IApple>();
builder.RegisterType<Apple>().As<IDisosableApple>().ExternallyOwned(); 

然后,您可以将 DisposableAppleFactory 注入需要创建和处置苹果的类中。

对于只需要一个与容器具有相同生命周期的苹果的类,您可以注入 IApple。

但是,您需要两者的事实可能表明您正在混合newables and injectables。 Apple 可能只是一个“可更新的”对象,即不需要由 IoC 容器管理的对象。

【讨论】:

  • 考虑到上面的对象模型,这可行,但不幸的是,与其他建议一样,如果 Apple 本身有任何依赖项也需要处理,它将“泄漏”。
猜你喜欢
  • 2012-11-06
  • 2010-12-06
  • 2021-05-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-31
相关资源
最近更新 更多