admin管理员组

文章数量:1122846

I am using DryIoc, but I think this is a general question applicable to any IoC container.

Consider a singleton service(Let's call it SingletonService). The SingletonService needs a new set of objects every time one of its functions is called. For simplicity let's say there is only one object and is called WorkerService. WorkerService will use unmanaged resources and therefor needs to be disposed.

I am unclear how to make sure that the WorkerService will be disposed as soon as it is not needed anymore. To have a reference to the container in SingletonService is a no go as far as I understand, because of the service locator anti-pattern.

One solution that comes to mind is to inject a WorkerServiceFactory into SingletonService. WorkerServiceFactory gets the container via injection. The factory then uses the container to get WorkerService and returns it to SingletonService. Is SingletonService the one responsible for disposing of WorkerService? Isn't there a guideline that states that objects created in the container should be disposed by the container? Alternatively I could use a scope but I am unclear how. If I were to create a new scope in SingletonService it would still contain a reference to the container(at least in DryIoc unless I am missing something). Creating the scope in the factory is meaningless since the scope would only be accessible by the factory.

How do you handle situations like this?

Edit 1:

I though about it a little more and came up with this:

using DryIoc;
using JetBrains.Annotations;

using var container = new Container();
container.Register<ISingletonService, SingletonService>(reuse: Reuse.Singleton);
container.Register<IWorkerService, WorkerService>(reuse: Reuse.Scoped);
container.Register<IWorkerServiceFactory, WorkerServiceFactory>(reuse: Reuse.Singleton);
var singleton = container.Resolve<ISingletonService>();
singleton.PrintData();
singleton.PrintData();
singleton.PrintData();
singleton.PrintData();
singleton.PrintData();


interface ISingletonService
{
    void PrintData();
}

class SingletonService(IWorkerServiceFactory workerFactory) : ISingletonService
{
    public void PrintData()
    {
        using var scope = workerFactory.Create(out var worker);
        Console.WriteLine(worker.GetData());
    }
}

interface IWorkerService
{
    string GetData();
}

class WorkerService : IWorkerService, IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("WorkerService was disposed");
    }

    public string GetData()
    {
        return "Hello Data";
    }
}

interface IWorkerServiceFactory
{
    [MustDisposeResource]
    IDisposable Create(out IWorkerService service);
}

class WorkerServiceFactory(IResolverContext ctx) : IWorkerServiceFactory
{
    public IDisposable Create(out IWorkerService service)
    {
        var scope = ctx.OpenScope();
        service = scope.Resolve<IWorkerService>();
        return scope;
    }
}

Essentially I am returning the WorkerService with an out parameter which allows me to return the scope created inside of my factory to the caller. This way SingletonService only cares about disposing the scope returned from the factory and the container handles everything else. Is this a sound approach?

I am using DryIoc, but I think this is a general question applicable to any IoC container.

Consider a singleton service(Let's call it SingletonService). The SingletonService needs a new set of objects every time one of its functions is called. For simplicity let's say there is only one object and is called WorkerService. WorkerService will use unmanaged resources and therefor needs to be disposed.

I am unclear how to make sure that the WorkerService will be disposed as soon as it is not needed anymore. To have a reference to the container in SingletonService is a no go as far as I understand, because of the service locator anti-pattern.

One solution that comes to mind is to inject a WorkerServiceFactory into SingletonService. WorkerServiceFactory gets the container via injection. The factory then uses the container to get WorkerService and returns it to SingletonService. Is SingletonService the one responsible for disposing of WorkerService? Isn't there a guideline that states that objects created in the container should be disposed by the container? Alternatively I could use a scope but I am unclear how. If I were to create a new scope in SingletonService it would still contain a reference to the container(at least in DryIoc unless I am missing something). Creating the scope in the factory is meaningless since the scope would only be accessible by the factory.

How do you handle situations like this?

Edit 1:

I though about it a little more and came up with this:

using DryIoc;
using JetBrains.Annotations;

using var container = new Container();
container.Register<ISingletonService, SingletonService>(reuse: Reuse.Singleton);
container.Register<IWorkerService, WorkerService>(reuse: Reuse.Scoped);
container.Register<IWorkerServiceFactory, WorkerServiceFactory>(reuse: Reuse.Singleton);
var singleton = container.Resolve<ISingletonService>();
singleton.PrintData();
singleton.PrintData();
singleton.PrintData();
singleton.PrintData();
singleton.PrintData();


interface ISingletonService
{
    void PrintData();
}

class SingletonService(IWorkerServiceFactory workerFactory) : ISingletonService
{
    public void PrintData()
    {
        using var scope = workerFactory.Create(out var worker);
        Console.WriteLine(worker.GetData());
    }
}

interface IWorkerService
{
    string GetData();
}

class WorkerService : IWorkerService, IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("WorkerService was disposed");
    }

    public string GetData()
    {
        return "Hello Data";
    }
}

interface IWorkerServiceFactory
{
    [MustDisposeResource]
    IDisposable Create(out IWorkerService service);
}

class WorkerServiceFactory(IResolverContext ctx) : IWorkerServiceFactory
{
    public IDisposable Create(out IWorkerService service)
    {
        var scope = ctx.OpenScope();
        service = scope.Resolve<IWorkerService>();
        return scope;
    }
}

Essentially I am returning the WorkerService with an out parameter which allows me to return the scope created inside of my factory to the caller. This way SingletonService only cares about disposing the scope returned from the factory and the container handles everything else. Is this a sound approach?

Share Improve this question edited Nov 21, 2024 at 21:38 patvax asked Nov 21, 2024 at 20:57 patvaxpatvax 6246 silver badges17 bronze badges 3
  • Firstly, I'd consider rethinking the pattern. Having a singleton that depends on multiple instances of a disposable resource is always going to be a little tricky. That being said, most IoC containers solve this with some form of lifetime scope concept. Then in the singleton you would new up a new lifetime scope, and would resolve the item from that new lifetime scope. Then somehow close / dispose the lifetime scope and all of the things it served up would be disposed as needed. var nestedScopedContainer = scopedContainer.OpenScope() – mjwills Commented Nov 21, 2024 at 22:18
  • @mjwills I would imagine that the pattern would be quiet common. After all you can't have everything statically loaded on startup. There will always be something short lived. If this thing needs a disposal we have the above case. To new up a scope at call site seems strange. Should a service really have access to the container? The container is normally needed to create a scope. – patvax Commented Nov 22, 2024 at 0:03
  • If you replace WorkerService with BackgroundService you'll see there's an entire documentation page about this Use scoped services within a BackgroundService. Either you use a factory with a using block or you create scopes inside the Singleton as needed. It's always the responsibility of the consumer (SingletonService) to dispose either the service or the scope – Panagiotis Kanavos Commented Nov 22, 2024 at 9:17
Add a comment  | 

1 Answer 1

Reset to default 1

This looks like an example of what Mark Seemann has named the Captive Dependency problem. That article explores the problem well, but, albeit via skim-reading, I cannot see any suggestions there on how to address it.

I've used Castle.Windsor extensively in the past. It has a feature called the Typed Factory Facility. I'm not suggesting you start using Castle.Windsor for this feature, but I think it's a pattern that gives you a possible answer to your question. TBH, you've practically arrived at yourself anyway.

It provides container-aware factory implementations using an interface-based convention. So it's essentially the same as your IWorkerServiceFactory. The key bit is the section on releasing the components you resolve via the factory. My perception of your current solution is that returning the container scope to the caller is giving the caller too much knowledge of the underlying infrastructure. The Typed Factory approach abstracts the lifespan management via the 'release/destroy' method on the interface. This way, the caller is responsible for the component's clean-up, but the mechanism is opaque. I would suggest doing something similar with your factory and encapsulating/hiding the underlying container mechanisms.

It looks like the Typed Factory feature also has support for IDisposable, but this appears to operate at the factory level. I'm not sure how I'd implement my own equivalent of that, but that doesn't mean you should overlook it.

So, something like:

interface IWorkerServiceFactory
{
    IWorkerService Create();
    void Release(IWorkerService workerService);
}

class WorkerServiceFactory(IResolverContext ctx) : IWorkerServiceFactory
{
    public IWorkerService Create()
    {
        // I don't know DryIoc, so I'm guessing...
        return ctx.Resolve<IWorkerService>();
    }

    public void Release(IWorkerService workerService)
    {
        return ctx.Release(workerService);
    }
}

本文标签: cHow to handle disposal of objects when using DIIoC containerStack Overflow