【问题标题】:How do you reconcile IDisposable and IoC?你如何协调 IDisposable 和 IoC?
【发布时间】:2009-06-12 16:48:43
【问题描述】:

我终于开始了解 C# 中的 IoC 和 DI,并且正在努力解决一些问题。我正在使用 Unity 容器,但我认为这个问题适用范围更广。

使用 IoC 容器分配实现 IDisposable 的实例让我大吃一惊!你怎么知道你是否应该 Dispose()?该实例可能是为您创建的(因此您应该 Dispose() 它),或者它可能是一个其生命周期在其他地方管理的实例(因此您最好不要)。代码中没有告诉你,事实上这可能会根据配置而改变!!!这对我来说似乎是致命的。

是否有任何 IoC 专家描述处理这种歧义的好方法?

【问题讨论】:

  • 我的解决方案是使用具有适当且经过良好编码的生命周期管理的 IoC:AutoFac 和 Castle Windsor 都有这样的(尽管它们的工作方式略有不同);在默认生命周期管理器下处理瞬态时,单元 2.1 根本失败..

标签: c# inversion-of-control unity-container idisposable


【解决方案1】:

您绝对不想在注入到您的类中的对象上调用 Dispose()。你不能假设你是唯一的消费者。最好的办法是将非托管对象包装在某个托管接口中:

public class ManagedFileReader : IManagedFileReader
{
    public string Read(string path)
    {
        using (StreamReader reader = File.OpenRead(path))
        {
            return reader.ReadToEnd();
        }
    }
}

这只是一个例子,如果我试图将文本文件读入字符串,我会使用 File.ReadAllText(path)。

另一种方法是注入工厂并自己管理对象:

public void DoSomething()
{
    using (var resourceThatShouldBeDisposed = injectedFactory.CreateResource())
    {
        // do something
    }
}

【讨论】:

  • 但我确实想注入 IDisposable 对象,此外,我可能需要也可能不需要在注入位置对它们进行 Dispose(),具体取决于配置。您的第一个示例在临时访问底层资源时有效,但如果资源更持久则无济于事。在第二个例子中,以这种方式注入工厂对我来说似乎很奇怪;这首先是 IoC 模型的主要功能之一。如果我将 IoC 应用于这个概念,我似乎最终回到了最初的问题。我认为容器本身需要参与其中,就像 AutoFac。
  • @Mr.腻子 - 依赖注入并没有消除对工厂的需求,它只是让一个(ab)使用它们变得不必要。例如,当您直到运行时才知道具体依赖项的类型,或者对于昂贵的条件依赖项(您甚至可能不需要的对象需要大量资源来创建),您可能想要注入工厂而不是对象本身。根据您的 DI 框架对 IDisposable 的支持,工厂注入可能是最好的方法 - 它肯定比许多替代方案更透明。 (+1)
  • 我认为这是个好建议。切勿直接注入 IDisposables;改用托管包装器 - 或者,使用允许在特定情况下显式处理的生命周期,例如数据库上下文,例如 StructureMap 中的 HttpContextScoped 或 Unity 中的 PerRequestLifetimeManager。
  • @L-Three And 使用一个真正理解 IDisposable 生命周期的 IoC 容器.. 但是在处理一次性用品时会有一些怪癖。到目前为止,我发现的唯一“确定”方法类似于 Castle 和 [类型化] 工厂:虽然瞬态最终会从根中释放(即没有“整体”资源泄漏),但确保立即子图的唯一方法处置是采用“工厂.释放”的策略。但是,这比尝试直接处理 IDisposable 服务要好:1)生命周期仍然由 IoC 处理; 2) 消费者不知道该组件是一次性的。
【解决方案2】:

AutoFac 通过允许创建嵌套容器来处理这个问题。容器完成后,它会自动处理其中的所有 IDisposable 对象。更多here

.. 当您解析服务时,Autofac 会跟踪已解析的一次性 (IDisposable) 组件。 在工作单元结束时,您处置相关的生命周期范围,Autofac 将自动清理/处置已解决的服务。

【讨论】:

  • 非常有趣。我对 AutoFac 一无所知。我认为用 Unity 做这样的事情不会太难。我将不得不考虑一下这一点。谢谢!
  • 使用 Unity 可能很难做到这一点,因为确定性处理从一开始就嵌入到 Autofac 的架构中。
  • Castle Windsor 是另一个可以让你做到这一点的容器,无论是使用子容器、作用域生活方式,还是使用container.Release 方法显式发布组件
  • StructureMap 也有一个嵌套容器的概念来处理这个问题。但是,它仅适用于瞬态,它不会处理在 http 上下文级别(有一个方法调用可以做到这一点)或单例(完全取决于你)范围内的东西。
  • AutoFac 似乎始终是每个容器/生命周期范围的 IDisposable-lifetimes(它工作得很好,与 Unity 不同,它确实提供了对瞬态的最终清理)。 Castle Windsor [还] 可以发布组件的子树,而无需创建显式范围,这在实现工厂之类的东西时很有用。 (但要让它像宣传的那样工作并允许提前发布,必须有明确的“factory.Release”调用,所以它在一定程度上转移了所有权。)
【解决方案3】:

这也经常让我感到困惑。尽管对此并不满意,但我始终得出结论,最好不要以瞬时方式返回 IDisposable 对象。

最近,我为自己重新表述了这个问题:这真的是 IoC 问题,还是 .net 框架问题?无论如何,处理都很尴尬。它没有有意义的功能目的,只有技术目的。所以这更像是一个框架问题,而不是 IoC 问题。

我喜欢 DI 的地方在于,我可以要求一份合同为我提供功能,而不必担心技术细节。我不是业主。不知道它在哪一层。不知道履行合同需要哪些技术,不担心寿命。我的代码看起来很干净,并且是高度可测试的。我可以在它们所属的层中实现职责。

所以,如果这条规则有一个例外需要我组织生命周期,那么让我们做一个例外。不管我喜不喜欢。如果实现接口的对象需要我处理它,我想知道它,从那时起我被触发使用尽可能短的对象。通过使用稍后处置的子容器来解决它的技巧可能仍然会导致我保持对象的存活时间比我应该的更长。对象的允许生存期是在注册对象时确定的。不是通过创建子容器并将其保留一段时间的功能。

所以只要我们开发人员需要担心处置(这会改变吗?)我会尝试注入尽可能少的临时一次性对象。 1. 我尝试使对象不是 IDisposable,例如不将一次性对象保持在类级别,而是在较小的范围内。 2. 我尝试使对象可重用,以便可以应用不同的生命周期管理器。

如果这不可行,我使用工厂来表明注入合约的用户是所有者,应该对此负责。

有一个警告:将合同实施者从非一次性更改为一次性将是一项重大更改。那时接口将不再注册,而是工厂接口。但我认为这也适用于其他场景。从那一刻起,忘记使用子容器就会产生内存问题。工厂方法会导致 IoC 解析异常。

一些示例代码:

using System;
using Microsoft.Practices.Unity;

namespace Test
{
    // Unity configuration
    public class ConfigurationExtension : UnityContainerExtension
    {
        protected override void Initialize()
        {
            // Container.RegisterType<IDataService, DataService>(); Use factory instead
            Container.RegisterType<IInjectionFactory<IDataService>, InjectionFactory<IDataService, DataService>>();
        }
    }

    #region General utility layer

    public interface IInjectionFactory<out T>
        where T : class
    {
        T Create();
    }

    public class InjectionFactory<T2, T1> : IInjectionFactory<T2>
        where T1 : T2
        where T2 : class

    {
        private readonly IUnityContainer _iocContainer;

        public InjectionFactory(IUnityContainer iocContainer)
        {
            _iocContainer = iocContainer;
        }

        public T2 Create()
        {
            return _iocContainer.Resolve<T1>();
        }
    }

    #endregion

    #region data layer

    public class DataService : IDataService, IDisposable
    {
        public object LoadData()
        {
            return "Test data";
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                /* Dispose stuff */
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }

    #endregion

    #region domain layer

    public interface IDataService
    {
        object LoadData();
    }

    public class DomainService
    {
        private readonly IInjectionFactory<IDataService> _dataServiceFactory;

        public DomainService(IInjectionFactory<IDataService> dataServiceFactory)
        {
            _dataServiceFactory = dataServiceFactory;
        }

        public object GetData()
        {
            var dataService = _dataServiceFactory.Create();
            try
            {
                return dataService.LoadData();
            }
            finally
            {
                var disposableDataService = dataService as IDisposable;
                if (disposableDataService != null)
                {
                    disposableDataService.Dispose();
                }
            }
        }
    }

    #endregion
}

【讨论】:

    【解决方案4】:

    我认为一般来说最好的方法是不丢弃已注入的东西;您必须假设注入器正在执行分配和释放。

    【讨论】:

    • 这很不方便,因为我经常要注入一次性物品。一般来说,IDisposable 的语义要求对象的创建者/所有者在正确的时间处理它。如果容器负责,那么实例的用户需要在完成时告诉容器。这很容易忘记。
    • 投了反对票,因为你答案的后半部分不好。大多数容器都有处理这个问题的方法,无论是嵌套容器还是 StructureMaps ReleaseAndDisposeAllHttpScopedObjects 方法。您仍然需要弄清楚如何妥善处理一次性用品......除非您喜欢用完连接等。
    【解决方案5】:

    这取决于 DI 框架。某些框架允许您指定是否要为每个注入的依赖项创建一个共享实例(始终使用相同的引用)。在这种情况下,您很可能不想丢弃。

    如果您可以指定要注入一个唯一的实例,那么您将需要处理(因为它是专门为您构建的)。不过,我对 Unity 并不熟悉 - 您必须查看文档以了解如何在那里进行这项工作。不过,它是 MEF 和其他一些我尝试过的属性的一部分。

    【讨论】:

      【解决方案6】:

      在容器前面放置一个门面也可以解决这个问题。此外,您可以扩展它以跟踪更丰富的生命周期,例如服务关闭和启动或 ServiceHost 状态转换。

      我的容器往往存在于实现 IServiceLocator 接口的 IExtension 中。它是统一的外观,并允许在 WCf 服务中轻松访问。另外,我可以从 ServiceHostBase 访问服务事件。

      您最终得到的代码将尝试查看任何已注册的单例或创建的任何类型是否实现了外观跟踪的任何接口。

      仍然不允许及时处理,因为您与这些事件有关,但它有点帮助。

      如果您想及时处理(也就是现在与服务关闭时相比)。你需要知道你拿到的item是disposable的,dispose它是业务逻辑的一部分,所以IDisposable应该是对象接口的一部分。并且可能应该验证与调用 dispose 方法相关的预期 untitests。

      【讨论】:

      • 这样你不会失去构造函数注入的优势吗?
      【解决方案7】:

      在 Unity 框架中,有两种方法可以注册注入的类:作为单例(解析时总是获得相同的类实例),或者在每次解析时获得一个新的类实例.

      在后一种情况下,您有责任在不需要时处理已解析的实例(这是一种非常合理的方法)。另一方面,当您释放容器(处理对象解析的类)时,所有单例对象也会自动释放。

      因此,使用 Unity 框架注入一次性对象显然没有问题。我不知道其他框架,但我想只要依赖注入框架足够稳固,它肯定会以某种方式处理这个问题。

      【讨论】:

      • 这是一种可怕的方法,对于需要处理的注入和未跟踪的对象没有任何“合理性”。这很复杂,因为组件不可能手动处理它们注入的依赖项,因为生命周期是未知的——单例、瞬态等。Unity(2.x)完全失败,因为在标准生命周期下,容器永远不会释放瞬态.
      • "在后一种情况下..." 客户不知道是第一种情况还是后一种情况,也不应该知道。它应该不知道注入对象的选定生命周期。我不同意 user2864740 关于 Unity 失败的说法。不幸的是,我认为这是我们必须处理的框架特征。
      猜你喜欢
      • 2011-02-07
      • 2011-05-08
      • 1970-01-01
      • 2012-04-19
      • 1970-01-01
      • 2013-01-18
      • 1970-01-01
      • 1970-01-01
      • 2013-11-29
      相关资源
      最近更新 更多