如何协调 IDisposable 和 IoC?

发布于 2024-07-24 03:36:23 字数 297 浏览 5 评论 0原文

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

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

国际奥委会专家能否描述一下处理这种歧义的好方法?

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.

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.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

旧人 2024-07-31 03:36:24

AutoFac 通过允许创建嵌套容器来处理此问题。 当容器使用完毕后,它会自动处理其中的所有 IDisposable 对象。 更多信息请此处

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

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.

.. 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.

牛↙奶布丁 2024-07-31 03:36:24

这也常常让我感到困惑。 尽管对此并不高兴,但我总是得出这样的结论:永远不要以瞬态方式返回 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
}

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.

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.

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.

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.

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.

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
}
妄断弥空 2024-07-31 03:36:24

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

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.

空宴 2024-07-31 03:36:24

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

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

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.

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.

寂寞清仓 2024-07-31 03:36:24

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

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

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

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

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

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.

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.

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.

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.

清晨说晚安 2024-07-31 03:36:24

在 Unity 框架中,有两种方法来注册注入的类:作为单例(当您解析它时,您始终会获得该类的相同实例),或者例如您在每次解析时都会获得该类的新实例。

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

因此,使用 Unity 框架注入一次性对象显然不存在任何问题。 我不了解其他框架,但我认为只要依赖注入框架足够可靠,它肯定会以某种方式处理这个问题。

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.

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.

2024-07-31 03:36:23

您绝对不想对注入到您的类中的对象调用 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
    }
}

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:

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.

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

public void DoSomething()
{
    using (var resourceThatShouldBeDisposed = injectedFactory.CreateResource())
    {
        // do something
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文