依赖注入和 IDisposable [英] Dependency Injection and IDisposable

查看:21
本文介绍了依赖注入和 IDisposable的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对 IDisposable 实现中的 Dispose() 方法和 Autofac 用法有点困惑

I'm a little bit confused about Dispose() methods in IDisposable implementations with Autofac usage

假设我对我的对象有一定的深度:

Say I have a certain depth to my objects:

  • Controller 依赖于 IManager;
  • Manager 依赖于 IRepository;
  • Repository 依赖于 ISession;
  • ISessionIDisposable.
  • Controller depends on IManager;
  • Manager depends on IRepository;
  • Repository depends on ISession;
  • ISession is IDisposable.

这导致以下对象图:

new Controller(
    new Manager(
        new Repository(
            new Session())));

我是否需要让我的 Manager 和 Repository 也实现 IDisposable 并在 Controller 中调用 Manager.Dispose(),在 Manager 中调用 Repository.Dispose() 等等,或者 Autofac 会自动知道我的调用堆栈中的哪些对象需要正确被处置?控制器对象已经是 IDisposable,因为它派生自基础 ASP.NET Web API 控制器

Do I need to make my Manager and Repository implement IDisposable as well and call Manager.Dispose() in Controller, Repository.Dispose() in Manager, etc. or will Autofac automagically know which objects in my call stack properly need to be disposed? Controller object is already IDisposable as it derives from base ASP.NET Web API controller

推荐答案

资源的一般规则是:

拥有资源的人负责处理它.

He who owns the resource is responsible of disposing of it.

这意味着如果一个类拥有一个资源,它应该使用与创建它相同的方法来处理它(在这种情况下,一次性被称为 临时一次性),或者如果这不可能,这通常意味着拥有的类必须实现 IDisposable,因此它可以在其 Dispose 方法中处理资源.

This means that if a class owns a resource, it should either dispose of it in the same method that it created it in (in which case the disposable is called an ephemeral disposable), or if that’s not possible, this generally means that the owning class must implement IDisposable, so it can dispose of the resource in its Dispose method.

但需要注意的是,一般来说,一个类应该只拥有一个资源,如果它负责创建.但是当一个资源被注入时,这意味着这个资源在消费者之前就已经存在了.消费者没有创建资源,在这种情况下应该处理它.尽管您可以将资源的所有权传递给使用者(并在传递所有权的类文档中说明这一点),但通常不应传递所有权,因为这会使您的代码复杂化并使应用程序变得非常脆弱.

But it is important to note that, in general, a class should only own a resource if it is responsible of its creation. But when a resource is injected, this means that this resource already existed before the consumer did. The consumer didn't create the resource and should in that case not dispose of it. Although you can pass on the ownership of the resource to the consumer (and communicate this in the class’s documentation that ownership is passed on), in general you should not pass on the ownership, because this complicates your code and makes the application very fragile.

尽管在某些情况下转移对象所有权的策略可能有意义,例如对于属于可重用 API 的类型(如 System.IO.StreamReader),但在以下情况下总是很糟糕处理属于对象图的组件(我们所谓的 注射剂).我将在下面解释原因.

Although the strategy of transferring ownership of objects might make sense in some cases, for instance for types that are part of a reusable API (like System.IO.StreamReader), it is always bad when dealing with components that are part of your object graph (our so called injectables). I’ll explain why below.

因此,即使您的 Controller 依赖于需要处理的依赖项,您的控制器也不应该处理它们:

So even if your Controller depends on dependencies that need to be disposed of, your controller should not dispose of them:

  • 因为消费者没有创建这样的依赖项,所以它不知道其依赖项的预期生命周期是多少.很可能依赖应该比消费者更长寿.在这种情况下,让消费者处理该依赖项会导致您的应用程序出现错误,因为下一个控制器将获得一个已经处理掉的依赖项,这将导致抛出 ObjectDisposedException.
  • 即使依赖项的生活方式与消费者的生活方式相同,处置仍然是一个坏主意,因为这会阻止您轻松地将该组件替换为将来可能具有更长生命周期的组件.一旦您将该组件替换为寿命更长的组件,您将不得不检查所有它的使用者,这可能会导致整个应用程序发生彻底的变化.换句话说,你这样做将违反开放/封闭原则——它应该无需进行彻底更改即可添加或替换功能.
  • 如果您的使用者能够处理其依赖项,这意味着您在该抽象上实现了 IDisposable.这意味着这种抽象泄露了实现细节——这违反了依赖倒置原则.在抽象上实现 IDisposable 时,您泄漏了实现细节,因为该抽象的每个实现 都不太可能需要确定性处理,因此您使用某个实现定义了抽象心里.消费者不应该知道任何关于实现的信息,无论它是否需要确定性处置.
  • 让抽象实现IDisposable 也会导致您违反接口隔离原则,因为抽象现在包含一个额外的方法(即 Dispose),并非所有消费者都需要调用.他们可能不需要调用它,因为——正如我已经提到的——资源可能比消费者更长寿.在这种情况下让它实现 IDisposable 是危险的,因为任何人都可以在它上面调用 Dispose 导致应用程序中断.如果您对测试更加严格,这也意味着您必须测试消费者不调用Dispose 方法.这将导致额外的测试代码.这是需要编写和维护的代码.
  • Because the consumer did not create such dependency, it has no idea what the expected lifetime of its dependency is. It could be very well that the dependency should outlive the consumer. Letting the consumer dispose of that dependency will in that case cause a bug in your application, because the next controller will get an already disposed of dependency, and this will cause an ObjectDisposedException to be thrown.
  • Even if the lifestyle of the dependency equals that of the consumer, disposing is still a bad idea, because this prevents you from easily replacing that component for one that might have a longer lifetime in the future. Once you replace that component for a longer-lived component, you will have to go through all it consumers, possibly causing sweeping changes throughout the application. In other words, you will be violating the Open/closed principle by doing that–it should be possible to add or replace functionality without making sweeping changes instead.
  • If your consumer is able to dispose of its dependency, this means that you implement IDisposable on that abstraction. This means this abstraction is leaking implementation details–that’s a violation of the Dependency Inversion Principle. You are leaking implementation details when implementing IDisposable on an abstraction, because it is very unlikely that every implementation of that abstraction needs deterministic disposal and so you defined the abstraction with a certain implementation in mind. The consumer should not have to know anything about the implementation, whether it needs deterministic disposal or not.
  • Letting that abstraction implement IDisposable also causes you to violate the Interface Segregation Principle, because the abstraction now contains an extra method (i.e. Dispose), that not all consumers need to call. They might not need to call it, because –as I already mentioned– the resource might outlive the consumer. Letting it implement IDisposable in that case is dangerous, because anyone can call Dispose on it causing the application to break. If you are more strict about testing, this also means you will have to test a consumer for not calling the Dispose method. This will cause extra test code. This is code that needs to be written and maintained.

因此,您应该实现实现IDisposable.这使抽象的任何消费者免于怀疑是否应该调用 Dispose(因为没有 Dispose 方法可以调用抽象).

So instead, you should only let the implementation implement IDisposable. This frees any consumer of the abstraction from the doubts whether it should or shouldn't call Dispose (because there is no Dispose method to call on the abstraction).

因为只有实现实现了 IDisposable 并且只有你的 Composition Root 创建了这个实现,它是 Composition Root 负责处理它.如果你的 DI 容器创建了这个资源,它也应该处理它.Autofac 实际上会为您做到这一点.您可以轻松验证这一点.如果您在不使用 DI 容器的情况下连接对象图(又名 Pure DI),这意味着您必须自己处理组合根中的这些对象.

Because only the implementation implements IDisposable and only your Composition Root creates this implementation, it is the Composition Root that is responsible of its disposal. In case your DI container creates this resource, it should also dispose it. Autofac will actually do this for you. You can easily verify this. In case you wire your object graphs without the use of a DI Container (a.k.a. Pure DI), it means that you will have to dispose of those objects in your Composition Root yourself.

请注意,此设计更改使您的代码更简单.在抽象上实现 IDisposable 并让消费者处理他们的依赖项将导致 IDisposable 像病毒一样在您的系统中传播并污染您的代码库.它展开,因为对于任何抽象,您总是可以想到需要清理其资源的实现,因此您必须在每个抽象上实现 IDisposable.这意味着每个需要一个或多个依赖项的实现也必须实现 IDisposable,并且这会级联对象图.这会为系统中的每个类增加大量代码和不必要的复杂性.

Note that this design change makes your code simpler. Implementing IDisposable on the abstraction and letting consumers dispose of their dependencies will cause IDisposable to spread out through your system like a virus and pollutes your code base. It spreads out, because for any abstraction, you can always think of an implementation that needs to clean up its resources, so you will have to implement IDisposable on every abstraction. This means that every implementation that takes one or more dependencies will also has to implement IDisposable as well, and this cascades up the object graph. This adds a lot of code and unnecessary complexity to each class in your system.

这篇关于依赖注入和 IDisposable的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆