你如何协调IDisposable和IoC?

时间:2022-09-25 10:28:13

I'm finally wrapping my head around IoC and DI in C#, and am struggling with some of the edges. I'm using the Unity container, but I think this question applies more broadly.

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

Using an IoC container to dispense instances that implement IDisposable freaks me out! How are you supposed to know if you should Dispose()? The instance might have been created just for you (and therefor you should Dispose() it), or it could be an instance whose lifetime is managed elsewhere (and therefor you'd better not). Nothing in the code tells you, and in fact this could change based on configuration!!! This seems deadly to me.

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

Can any IoC experts out there describe good ways to handle this ambiguity?

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

7 个解决方案

#1


AutoFac handles this by allowing the creation of a nested container. When the container is finished with, it automatically disposes of all IDisposable objects within it. More here.

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

.. As you resolve services, Autofac tracks disposable (IDisposable) components that are resolved. At the end of the unit of work, you dispose of the associated lifetime scope and Autofac will automatically clean up/dispose of the resolved services.

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

#2


You definitely do not want to call Dispose() on an object that was injected into your class. You can't make the assumption that you are the only consumer. Your best bet is to wrap your unmanaged object in some managed interface:

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

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

That is just an example, I would use File.ReadAllText(path) if I were trying to read a text file into a string.

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

Another approach is to inject a factory and manage the object yourself:

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

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

#3


This has puzzled me frequently as well. Though not happy about it, I always came to the conclusion that never returning an IDisposable object in a transient way was best.

这也让我经常困惑。尽管对此并不满意,但我总是得出结论,永远不会以瞬态方式返回IDisposable对象是最好的。

Recently, I rephrased the question for myself: Is this really an IoC issue, or a .net framework issue? Disposing is awkward anyway. It has no meaningful functional purpose, only technical. So it's more a framework issue that we have to deal with, than an IoC issue.

最近,我为自己重新提出了这个问题:这真的是IoC问题,还是.net框架问题?无论如何,处置很尴尬。它没有意义的功能目的,只有技术目的。因此,与IoC问题相比,它更像是我们必须处理的框架问题。

What I like about DI is that I can ask for a contract providing me functionality, without having to bother about the technical details. I'm not the owner. No knowledge about which layer it's in. No knowledge about which technologies are required to fulfil the contract, no worries about lifetime. My code looks nice and clean, and is highly testable. I can implement responsibilities in the layers where they belong.

我喜欢DI的是我可以要求合同为我提供功能,而不必担心技术细节。我不是主人。不知道它在哪个层。不知道履行合同需要哪些技术,不用担心终生。我的代码看起来很干净,而且非常可测试。我可以在他们所属的图层中实施职责。

So if there's an exception to this rule that does require me to organise the lifetime, let's make that exception. Whether I like it or not. If the object implementing the interface requires me to dispose it, I want to know about it since then I am triggered to use the object as short as possible. A trick by resolving it using a child container which is disposed some time later on might still cause me keeping the object alive longer than I should. The allowed lifetime of the object is determined when registering the object. Not by the functionality that creates a child container and holds on to that for a certain period.

因此,如果这条规则存在例外情况,需要我组织生命周期,那么让我们做出异常。不管我喜不喜欢。如果实现接口的对象需要我处理它,我想知道它从那时起我被触发使用尽可能短的对象。使用稍后放置的子容器解决它的技巧可能仍然会让我保持对象的活动时间超过我应该的时间。在注册对象时确定对象的允许寿命。不是通过创建子容器的功能并在一段时间内保留它。

So as long as we developers need to worry about disposing (will that ever change?) I will try to inject as few transient disposable objects as possible. 1. I try to make the object not IDisposable, for example by not keeping disposable objects on class level, but in a smaller scope. 2. I try to make the object reusable so that a different lifetime manager can be applied.

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

If this is not feasible, I use a factory to indicate that the user of the injected contract is owner and should take responsibility for it.

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

There is one caveat: changing a contract implementer from non-disposable to disposable will be a breaking change. At that time the interface will no longer be registered, but the interface to the factory. But I think this applies to other scenario's as well. Forgetting to use a child container will from that moment on give memory issues. The factory approach will cause an IoC resolve exception.

有一点需要注意:将合同执行者从非一次性改为一次性将是一个突破性的变化。那时接口将不再注册,而是工厂的接口。但我认为这也适用于其他情况。忘记使用子容器将从那一刻开始给出内存问题。工厂方法将导致IoC解决异常。

Some example code:

一些示例代码:

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


I think in general the best approach is to simply not Dispose of something which has been injected; you have to assume that the injector is doing the allocation and deallocation.

我认为一般来说,最好的办法就是不要处理已注入的东西;你必须假设注射器正在进行分配和释放。

#5


This depends on the DI framework. Some frameworks allow you to specify whether you want a shared instance (always using the same reference) for every dependency injected. In this case, you most likely do not want to dispose.

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

If you can specify that you want a unique instance injected, then you will want to dispose (since it was being constructed for you specifically). I'm not as familiar with Unity, though - you'd have to check the docs as to how to make this work there. It's part of the attribute with MEF and some others I've tried, though.

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

#6


Putting a facade in front of the container can resolve this as well. Plus you can extend it to keep track of a more rich life cycle like service shutdowns and startups or ServiceHost state transitions.

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

My container tends to live in an IExtension that implements the IServiceLocator interface. It is a facade for unity, and allows for easy access in WCf services. Plus I have access to the service events from the ServiceHostBase.

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

The code you end up with will attempt to see if any singleton registered or any type created implements any of the interfaces that the facade keeps track of.

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

Still does not allow for the disposing in a timely manner as you are tied to these events but it helps a bit.

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

If you want to dispose in a timely manner (aka, now v.s. upon service shutdown). You need to know that the item you get is disposable, it is part of the business logic to dispose of it, so IDisposable should be part of the interface of the object. And there probably should be verification of expectations untitests related to the dispose method getting called.

如果你想及时处理(也就是现在v.s.服务关闭时)。您需要知道您获得的项目是一次性的,它是处理它的业务逻辑的一部分,因此IDisposable应该是对象接口的一部分。并且可能应该验证与调用dispose方法相关的期望值。

#7


In the Unity framework, there are two ways to register the injected classes: as singletons (you get always the same instance of the class when you resolve it), or such as you get a new instance of the class on each resolution.

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

In the later case, you have the responsibility of disposing the resolved instance once you don't need it (which is a quite reasonable approach). On the other hand, when you dispose the container (the class that handles object resolutions), all the singleton objects are automatically disposed as well.

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

Therefore, there are apparently no issues with injected disposable objects with the Unity framework. I don't know about other frameworks, but I suppose that as long as a dependency injection framework is solid enough, it for sure handles this issue in one way or another.

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

#1


AutoFac handles this by allowing the creation of a nested container. When the container is finished with, it automatically disposes of all IDisposable objects within it. More here.

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

.. As you resolve services, Autofac tracks disposable (IDisposable) components that are resolved. At the end of the unit of work, you dispose of the associated lifetime scope and Autofac will automatically clean up/dispose of the resolved services.

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

#2


You definitely do not want to call Dispose() on an object that was injected into your class. You can't make the assumption that you are the only consumer. Your best bet is to wrap your unmanaged object in some managed interface:

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

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

That is just an example, I would use File.ReadAllText(path) if I were trying to read a text file into a string.

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

Another approach is to inject a factory and manage the object yourself:

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

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

#3


This has puzzled me frequently as well. Though not happy about it, I always came to the conclusion that never returning an IDisposable object in a transient way was best.

这也让我经常困惑。尽管对此并不满意,但我总是得出结论,永远不会以瞬态方式返回IDisposable对象是最好的。

Recently, I rephrased the question for myself: Is this really an IoC issue, or a .net framework issue? Disposing is awkward anyway. It has no meaningful functional purpose, only technical. So it's more a framework issue that we have to deal with, than an IoC issue.

最近,我为自己重新提出了这个问题:这真的是IoC问题,还是.net框架问题?无论如何,处置很尴尬。它没有意义的功能目的,只有技术目的。因此,与IoC问题相比,它更像是我们必须处理的框架问题。

What I like about DI is that I can ask for a contract providing me functionality, without having to bother about the technical details. I'm not the owner. No knowledge about which layer it's in. No knowledge about which technologies are required to fulfil the contract, no worries about lifetime. My code looks nice and clean, and is highly testable. I can implement responsibilities in the layers where they belong.

我喜欢DI的是我可以要求合同为我提供功能,而不必担心技术细节。我不是主人。不知道它在哪个层。不知道履行合同需要哪些技术,不用担心终生。我的代码看起来很干净,而且非常可测试。我可以在他们所属的图层中实施职责。

So if there's an exception to this rule that does require me to organise the lifetime, let's make that exception. Whether I like it or not. If the object implementing the interface requires me to dispose it, I want to know about it since then I am triggered to use the object as short as possible. A trick by resolving it using a child container which is disposed some time later on might still cause me keeping the object alive longer than I should. The allowed lifetime of the object is determined when registering the object. Not by the functionality that creates a child container and holds on to that for a certain period.

因此,如果这条规则存在例外情况,需要我组织生命周期,那么让我们做出异常。不管我喜不喜欢。如果实现接口的对象需要我处理它,我想知道它从那时起我被触发使用尽可能短的对象。使用稍后放置的子容器解决它的技巧可能仍然会让我保持对象的活动时间超过我应该的时间。在注册对象时确定对象的允许寿命。不是通过创建子容器的功能并在一段时间内保留它。

So as long as we developers need to worry about disposing (will that ever change?) I will try to inject as few transient disposable objects as possible. 1. I try to make the object not IDisposable, for example by not keeping disposable objects on class level, but in a smaller scope. 2. I try to make the object reusable so that a different lifetime manager can be applied.

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

If this is not feasible, I use a factory to indicate that the user of the injected contract is owner and should take responsibility for it.

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

There is one caveat: changing a contract implementer from non-disposable to disposable will be a breaking change. At that time the interface will no longer be registered, but the interface to the factory. But I think this applies to other scenario's as well. Forgetting to use a child container will from that moment on give memory issues. The factory approach will cause an IoC resolve exception.

有一点需要注意:将合同执行者从非一次性改为一次性将是一个突破性的变化。那时接口将不再注册,而是工厂的接口。但我认为这也适用于其他情况。忘记使用子容器将从那一刻开始给出内存问题。工厂方法将导致IoC解决异常。

Some example code:

一些示例代码:

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


I think in general the best approach is to simply not Dispose of something which has been injected; you have to assume that the injector is doing the allocation and deallocation.

我认为一般来说,最好的办法就是不要处理已注入的东西;你必须假设注射器正在进行分配和释放。

#5


This depends on the DI framework. Some frameworks allow you to specify whether you want a shared instance (always using the same reference) for every dependency injected. In this case, you most likely do not want to dispose.

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

If you can specify that you want a unique instance injected, then you will want to dispose (since it was being constructed for you specifically). I'm not as familiar with Unity, though - you'd have to check the docs as to how to make this work there. It's part of the attribute with MEF and some others I've tried, though.

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

#6


Putting a facade in front of the container can resolve this as well. Plus you can extend it to keep track of a more rich life cycle like service shutdowns and startups or ServiceHost state transitions.

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

My container tends to live in an IExtension that implements the IServiceLocator interface. It is a facade for unity, and allows for easy access in WCf services. Plus I have access to the service events from the ServiceHostBase.

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

The code you end up with will attempt to see if any singleton registered or any type created implements any of the interfaces that the facade keeps track of.

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

Still does not allow for the disposing in a timely manner as you are tied to these events but it helps a bit.

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

If you want to dispose in a timely manner (aka, now v.s. upon service shutdown). You need to know that the item you get is disposable, it is part of the business logic to dispose of it, so IDisposable should be part of the interface of the object. And there probably should be verification of expectations untitests related to the dispose method getting called.

如果你想及时处理(也就是现在v.s.服务关闭时)。您需要知道您获得的项目是一次性的,它是处理它的业务逻辑的一部分,因此IDisposable应该是对象接口的一部分。并且可能应该验证与调用dispose方法相关的期望值。

#7


In the Unity framework, there are two ways to register the injected classes: as singletons (you get always the same instance of the class when you resolve it), or such as you get a new instance of the class on each resolution.

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

In the later case, you have the responsibility of disposing the resolved instance once you don't need it (which is a quite reasonable approach). On the other hand, when you dispose the container (the class that handles object resolutions), all the singleton objects are automatically disposed as well.

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

Therefore, there are apparently no issues with injected disposable objects with the Unity framework. I don't know about other frameworks, but I suppose that as long as a dependency injection framework is solid enough, it for sure handles this issue in one way or another.

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