2009-06-12 42 views
41

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

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

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

+1

我的解决办法是使用一个IoC和适当和公编纂寿命管理:AutoFac和城堡温莎有这样的(尽管它们的工作方式略有不同);在处理默认终身管理器下的瞬变时,2.1单元根本失败。 – user2864740 2015-03-03 21:00:06

回答

7

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

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

+0

非常有趣。我什么都不知道AutoFac。我认为用Unity做这样的事情不会太难。我将不得不考虑这一点。谢谢! – 2009-06-13 07:07:30

+1

Unity可能很难做到这一点,因为确定性处置自一开始就已经嵌入到Autofac架构中。 – 2009-06-13 17:56:28

+1

Castle Windsor是另一个容器,可以使用子容器,范围生活方式或通过使用`container.Release`方法明确释放组件来实现这个功能。 – 2010-05-18 11:36:07

2

我认为一般来说最好的方法是简单地不处理已注入的东西;您必须假定喷油器正在执行分配和重新分配。

+3

这是非常不方便的,因为我经常要注入一次性物体。通常,IDisposable的语义要求对象的创建者/拥有者在正确的时间处理它。如果容器负责,那么实例的用户需要告诉容器何时完成。这很容易忘记。 – 2009-06-13 07:05:36

+1

Downvoted,因为你答案的后半部分是不好的。大多数容器都有处理这个问题的方法,可以是嵌套容器,也可以是StructureMaps`ReleaseAndDisposeAllHttpScopedObjects`方法。你仍然需要弄清楚一次性物品如何妥善处置......除非你喜欢用尽连接等。 – Andy 2012-07-16 18:00:20

2

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

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

17

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

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

这只是一个例子,我会用File.ReadAllText(路径)如果我想读的文本文件转换成字符串。

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

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

在统一框架下,有两种方法来注册注入类:为单身(你永远是同一个实例当你解决它时),或者你在每个分辨率上得到一个新类的实例。

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

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

2

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

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

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

仍然不允许及时处置,因为你被绑定到这些事件,但它有点帮助。

如果您希望及时处置(也就是现在的服务关闭v.s.)。你需要知道你得到的物品是一次性的,它是处理它的业务逻辑的一部分,所以IDisposable应该是物体接口的一部分。并且可能应该验证与处置方法相关的未初始化的期望。

4

这也让我感到困惑。虽然对此并不满意,但我总是得出结论:永远不会以暂时的方式返回一个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 
}