依赖注入和IDisposable

问题描述 投票:8回答:1

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

说我的对象有一定的深度:

  • Controller依赖于IManager;
  • Manager依赖于IRepository;
  • Repository依赖于ISession;
  • ISessionIDisposable

这导致以下对象图:

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

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

.net dependency-injection inversion-of-control autofac idisposable
1个回答
26
投票

资源的一般规则是:

拥有该资源的人负责处理它。

这意味着如果一个类拥有一个资源,它应该以与它创建它相同的方法处理它,或者如果这不可能,这通常意味着拥有类必须实现IDisposable,因此它可以将资源部署在其中Dispose方法。

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

虽然在某些情况下转移对象所有权的策略可能有意义,例如对于可重用API的一部分类型(如System.IO.StreamReader),在处理作为对象图的一部分的组件时,它总是很糟糕(我们所谓的injectables)。我将在下面解释原因。

因此,即使您的Controller依赖于需要处理的依赖关系,您的控制器也不应该丢弃它们。原因很简单:

  • 由于消费者没有创建这种依赖关系,因此不知道该依赖关系的预期生命周期是什么。很可能依赖性应该比消费者的生命周期长。让消费者在这种情况下处理依赖将导致应用程序中的错误,因为下一个控制器将获得已经处置的依赖关系,这将导致抛出ObjectDisposedException
  • 即使依赖的生活方式等于消费者的生活方式,处置仍然是一个坏主意,因为这可以防止您轻松地将该组件替换为可能具有更长寿命的组件。一旦为更长寿命的组件替换该组件,您将必须遍历处理该依赖关系的所有组件,从而使您在整个应用程序中进行彻底的更改。换句话说,你将通过这样做违反Open/closed principle,因为它应该可以添加或替换功能而不需要进行彻底的更改。
  • 如果您的使用者能够处置此依赖关系,这意味着您在该抽象上实现IDisposable。这意味着这种抽象漏掉了实现细节;这是对Dependency Inversion Principle的违反。在抽象上实现IDisposable时,您正在泄漏实现细节,因为该抽象的每个实现都不太可能需要确定性处理,因此您在考虑某个实现时定义了抽象。消费者不应该对实施有任何了解,无论是否需要确定性处置。
  • 让抽象实现IDisposable也会导致你违反Interface Segregation Principle,因为抽象现在包含一个额外的方法(即Dispose),并非所有消费者都需要调用。他们可能不需要打电话,因为 - 我们已经意识到 - 资源可能比消费者更长久。在这种情况下让它实现IDisposable是危险的,因为任何人都可以调用Dispose导致应用程序中断。如果我们对测试非常严格,这也意味着我们必须测试消费者没有调用Dispose方法。考虑一下这将导致的额外测试代码。这是需要编写和维护的代码。

所以相反,我们应该只让实现实现IDisposable。这使得抽象的任何消费者都不会怀疑它是否应该调用Dispose(因为没有Dispose方法来调用抽象)。

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

要意识到这会使您的代码更简单。在抽象上实现IDisposable并让消费者处理它们的依赖关系将导致IDisposable像病毒一样通过你的系统传播并污染你的代码库。

它展开,因为您总是可以想到需要清理其资源的抽象实现,因此您必须在每个抽象上实现IDisposable。这意味着每个需要一个或多个依赖项的实现也必须实现IDisposable,这会爬上对象图。这为系统中的每个类增加了大量代码和不必要的复杂性。

© www.soinside.com 2019 - 2024. All rights reserved.