42

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

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

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

4

7 回答 7

17

您绝对不想在注入到您的类中的对象上调用 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
    }
}
于 2009-06-18T04:35:06.257 回答
7

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

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

于 2009-06-12T16:59:55.167 回答
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
}
于 2015-03-05T13:49:41.090 回答
2

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

于 2009-06-12T16:52:48.460 回答
2

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

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

于 2009-06-12T16:55:29.640 回答
2

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

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

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

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

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

于 2010-03-12T21:04:32.457 回答
1

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

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

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

于 2009-09-19T20:28:26.797 回答