你如何协调IDisposable和IoC?

我终于用C#围绕IoC和DI进行了包围,并且正在挣扎着一些边缘。 我正在使用Unity容器,但我认为这个问题更广泛地适用。

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

任何IoC专家都可以描述处理这种歧义的好方法吗?

AutoFac通过允许创build一个嵌套容器来处理这个问题。 当容器完成时,它会自动处理其中的所有IDisposable对象。 更多在这里 。

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

您绝对不想在注入到您的类中的对象上调用Dispose()。 你不能假设你是唯一的消费者。 你最好的select是把你的非托pipe对象包装在一些托pipe界面中:

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

这只是一个例子,我会使用File.ReadAllText(path),如果我试图读取文本文件到一个string。

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

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

这也使我经常困惑。 虽然对此并不满意,但我总是得出这样一个结论,即永远不会以暂时的方式返回一个可丢弃的对象是最好的。

最近,我为自己解释了这个问题:这是一个IoC问题还是.net框架问题? 无论如何处置是尴尬的。 它没有意义的function目的,只有技术。 所以这是一个我们不得不处理的框架问题,而不是一个IoC问题。

我喜欢DI的是我可以要求一个合同提供我的function,而不必打扰技术细节。 我不是主人。 没有关于它在哪一层的知识。没有关于哪些技术需要履行合同的知识,不用担心一生。 我的代码看起来不错,干净,而且是高度可testing的。 我可以在他们所属的层面实施责任。

所以如果这个规则有一个例外,那就要求我组织整个生命周期,让我们来做个例外。 不pipe我喜不喜欢 如果实现这个接口的对象需要我去处理,那么我想知道它,因为我被触发了尽可能短的对象。 使用一段时间后放置的子容器解决这个问题的一个窍门仍然可能导致我保持对象的存活时间比我应该的时间更长。 注册对象时确定对象的允许使用期限。 不是通过创build子容器的function,并持续一段时间。

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

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

有一个警告:将合同执行者从非一次性更换为一次性将是一个突破性的变化。 那时界面将不再被注册,而是到工厂的界面。 但我认为这也适用于其他情况。 忘记使用子容器会从那一刻起给内存问题。 工厂方法将导致IoC解决exception。

一些示例代码:

 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 } 

我认为一般来说,最好的办法是简单地不处理已经注入的东西; 你必须假定喷油器正在进行分配和释放。

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

如果你可以指定你想要一个唯一的实例注入,那么你将要处置(因为它是专门为你构build的)。 但是我不太了解Unity,因此您必须查看文档以了解如何在此工作。 这是MEF和我尝试过的其他一些属性的一部分。

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

我的容器倾向于生活在实现IServiceLocator接口的IExtension中。 这是一个统一的门面,并允许在WCf服务方便访问。 另外我可以访问ServiceHostBase中的服务事件。

你最终的代码将尝试查看是否有任何注册的单例或创build的任何types实现了外观跟踪的任何接口。

仍然不能及时处置,因为你被束缚了这些事件,但它有点帮助。

如果你想及时处理(也就是现在和服务closures)。 你需要知道你得到的物品是一次性的,它是处理它的业务逻辑的一部分,所以IDisposable应该是对象接口的一部分。 而且可能还需要validation与处理方法相关的未定义方法。

在Unity框架中,有两种注册注入类的方法:作为单例(当你解决这个问题的时候,你总是得到同一个类的实例),或者你在每个分辨率上得到一个类的新实例。

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

因此,使用Unity框架注入一次性对象显然没有问题。 我不知道其他的框架,但我认为,只要一个dependency injection框架足够稳固,它肯定会以这样或那样的方式处理这个问题。