ServiceContainer, IoC, and disposable objects

爷,独闯天下 提交于 2020-01-12 03:35:09

问题


I have a question, and I'm going to tag this subjective since that's what I think it evolves into, more of a discussion. I'm hoping for some good ideas or some thought-provokers. I apologize for the long-winded question but you need to know the context.

The question is basically:

  • How do you deal with concrete types in relation to IoC containers? Specifically, who is responsible for disposing them, if they require disposal, and how does that knowledge get propagated out to the calling code?

Do you require them to be IDisposable? If not, is that code future-proof, or is the rule that you cannot use disposable objects? If you enforce IDisposable-requirements on interfaces and concrete types to be future-proof, whose responsibility is objects injected as part of constructor calls?


Edit: I accepted the answer by @Chris Ballard since it's the closest one to the approach we ended up with.

Basically, we always return a type that looks like this:

public interface IService<T> : IDisposable
    where T: class
{
    T Instance { get; }
    Boolean Success { get; }
    String FailureMessage { get; } // in case Success=false
}

We then return an object implementing this interface back from both .Resolve and .TryResolve, so that what we get in the calling code is always the same type.

Now, the object implementing this interface, IService<T> is IDisposable, and should always be disposed of. It's not up to the programmer that resolves a service to decide whether the IService<T> object should be disposed or not.

However, and this is the crucial part, whether the service instance should be disposed or not, that knowledge is baked into the object implementing IService<T>, so if it's a factory-scoped service (ie. each call to Resolve ends up with a new service instance), then the service instance will be disposed when the IService<T> object is disposed.

This also made it possible to support other special scopes, like pooling. We can now say that we want minimum 2 service instances, maximum 15, and typically 5, which means that each call to .Resolve will either retrieve a service instance from a pool of available objects, or construct a new one. And then, when the IService<T> object that holds the pooled service is disposed of, the service instance is released back into its pool.

Sure, this made all code look like this:

using (var service = ServiceContainer.Global.Resolve<ISomeService>())
{
    service.Instance.DoSomething();
}

but it's a clean approach, and it has the same syntax regardless of the type of service or concrete object in use, so we chose that as an acceptable solution.


Original question follows, for posterity


Long-winded question comes here:

We have a IoC container that we use, and recently we discovered what amounts to a problem.

In non-IoC code, when we wanted to use, say, a file, we used a class like this:

using (Stream stream = new FileStream(...))
{
    ...
}

There was no question as to whether this class was something that held a limited resource or not, since we knew that files had to be closed, and the class itself implemented IDisposable. The rule is simply that every class we construct an object of, that implements IDisposable, has to be disposed of. No questions asked. It's not up to the user of this class to decide if calling Dispose is optional or not.

Ok, so on to the first step towards the IoC container. Let's assume we don't want the code to talk directly to the file, but instead go through one layer of indirection. Let's call this class a BinaryDataProvider for this example. Internally, the class is using a stream, which is still a disposable object, so the above code would be changed to:

using (BinaryDataProvider provider = new BinaryDataProvider(...))
{
    ...
}

This doesn't change much. The knowledge that the class implements IDisposable is still here, no questions asked, we need to call Dispose.

But, let's assume that we have classes that provide data that right now doesn't use any such limited resources.

The above code could then be written as:

BinaryDataProvider provider = new BinaryDataProvider();
...

OK, so far so good, but here comes the meat of the question. Let's assume we want to use an IoC container to inject this provider instead of depending on a specific concrete type.

The code would then be:

IBinaryDataProvider provider =
    ServiceContainer.Global.Resolve<IBinaryDataProvider>();
...

Note that I assume there is an independent interface available that we can access the object through.

With the above change, what if we later on want to use an object that really should be disposed of? None of the existing code that resolves that interface is written to dispose of the object, so what now?

The way we see it, we have to pick one solution:

  • Implement runtime checking that checks that if a concrete type that is being registered implements IDisposable, require that the interface it is exposed through also implements IDisposable. This is not a good solution
  • Enfore a constraint on the interfaces being used, they must always inherit from IDisposable, in order to be future-proof
  • Enforce runtime that no concrete types can be IDisposable, since this is specifically not handled by the code using the IoC container
  • Just leave it up to the programmer to check if the object implements IDisposable and "do the right thing"?
  • Are there others?

Also, what about injecting objects in constructors? Our container, and some of the other containers we've looked into, is capable of injecting a fresh object into a parameter to a constructor of a concrete type. For instance, if our BinaryDataProvider need an object that implements the ILogging interface, if we enforce IDispose-"ability" on these objects, whose responsibility is it to dispose of the logging object?

What do you think? I want opinions, good and bad.


回答1:


(Disclaimer: I'm answering this based on java stuff. Although I program C# I haven't proxied anything in C# but I know it's possible. Sorry about the java terminology)

You could let the IoC framework inspect the object being constructed to see if it supports IDisposable. If not, you could use a dynamic proxy to wrap the actual object that the IoC framework provides to the client code. This dynamic proxy could implement IDisposable, so that you'd always deliver a IDisposable to the client. As long as you're working with interfaces that should be fairly simple ?

Then you'd just have the problem of communicating to the developer when the object is an IDisposable. I'm not really sure how this'd be done in a nice manner.




回答2:


One option might be to go with a factory pattern, so that the objects created directly by the IoC container never need to be disposed themselves, eg

IBinaryDataProviderFactory factory =
    ServiceContainer.Global.Resolve<IBinaryDataProviderFactory>();
using(IBinaryDataProvider provider = factory.CreateProvider())
{
    ...
}

Downside is added complexity, but it does mean that the container never creates anything which the developer is supposed to dispose of - it is always explicit code which does this.

If you really want to make it obvious, the factory method could be named something like CreateDisposableProvider().




回答3:


You actually came up with a very dirty solution: your IService contract violates the SRP, wich is a big no-no.

What I recommend is to distinguish so-called "singleton" services from so-called "prototype" services. Lifetime of "singleton" ones is managed by the container, which may query at runtime whether a particular instance implements IDisposable and invoke Dispose() on shutdown if so.

Managing prototypes, on the other hand, is totally the responsibility of the calling code.



来源:https://stackoverflow.com/questions/556580/servicecontainer-ioc-and-disposable-objects

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