Autofac: How to limit the lifetime of an IDisposable object without passing around the IoC container

感情迁移 提交于 2019-12-03 06:16:15
Nicholas Blumhardt

The other answers here are insightful, but have a problem. In both cases, if Apple has other dependencies that need disposal, correct cleanup won't happen.

Autofac 2 provides a new feature to help here, called "owned instances". I noticed that your registration code is Autofac 1.4, so if you're unable to upgrade let me know (there are other, less transparent, ways to do this.)

Register Apple as usual (not externally owned):

builder.RegisterType<Apple>().As<IApple>();

Declare AppleFactory as:

public delegate Owned<IApple> AppleFactory();

In Autofac 2, you do not need to call RegisterGeneratedFactory() anymore - this is automatic.

Then, in HorseKeeper, feed the horse like this:

public void FeedHorse()
{
    using (var apple = appleFactory())
    {
        horse.Eat(apple.Value);
    }
}

(Note the .Value property to get the underlying IApple.

At the end of the using block the apple, plus all of its dependencies, will be cleaned up.

Any other components that use IApple directly (as dependencies) will get the usual behaviour.

The only way is to modify the Apple registration with the ExternallyOwned modifier. This instructs Autofac to not track the object for disposal, but rather let someone external (your code) handle the disposal. But as you state, you will now have to make sure that all instances of Apple are disposed manually, since you will get no automatic help from Autofac.

builder.RegisterType<Apple>().As<IApple>().ExternallyOwned();

With this registration your Feed code will work as expected, though.

Note: on the discussion whether the interface should inherit IDisposable or not: IMO, when an interface inherits IDisposable, this is an indication to the "consuming" developer that the instance should be disposed at some point in time. In the case of IApple, since that interface is also IDisposable, the developer should make sure to dispose instances (and must also then be registered as ExternallyOwned). On the other hand, if the Apple class looked like this:

class Apple: IApple, IDisposable
{ }

consumers of the IApple is now fully unaware of the fact that instances is IDisposable. In this case we'll let the container handle disposal.

So my conclusion is that it is up to me as the developer of Apple and IApple to choose whether I'll require consumers to handle disposal or leave it up to a container.

If you sometimes want to manage the lifetime of Apple instances yourself, and sometimes let the container handle it, then you can define two interfaces:

public IApple
{
   void Consume();
}

public IDisposableApple : IApple, IDisposable
{
}

And then register the class twice:

builder.RegisterType<Apple>().As<IApple>();
builder.RegisterType<Apple>().As<IDisosableApple>().ExternallyOwned(); 

You can then inject a DisposableAppleFactory into classes that need to create and dispose apples.

For classes which just need an apple with the same lifetime as the container, you inject IApple instead.

However, the fact that you need both may indicate that you are mixing newables and injectables. Apple may simply be a "newable" object, i.e. one that doesn't need to be managed by the IoC container.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!