191180rk
191180rk

Reputation: 905

.NET Core dependency injection - How to dispose manually created instances?

Like the below code, I have to use the factory/func way of registration with container as I don't know the value of "userId" property ahead of time but only during runtime. This is working & no issues with functionality aspects.

container.AddSingleton<IBLogic, BLogic>(); //explicitly registration 'BLogic' service object

container.AddSingleton<Func<string, IDBCache>>
        (p=> (userId) => new IDBCache(userId, p.GetService<IBLogic>()));

Since here I'm using "new IDBCache", as per the link framework does not dispose of the services automatically.

Questions:

  1. To make framework to dispose of the services automatically, is there any way out?

  2. container.AddSingleton<Func<string, IDBCache>> (p=> (userId) => new IDBCache(userId, p.GetService()));

Since I'm just registering the definition of func/factory not the service object(like 'BLogic') as such, does AddSingleton provides any advantage over using 'AddScoped' or 'AddTransisent' like below?

container.AddTransisent<Func<string, IDBCache>>
        (p=> (userId) => new IDBCache(userId, p.GetService<IBLogic>()));

Upvotes: 2

Views: 1980

Answers (1)

Steven
Steven

Reputation: 172606

There's no easy fix for this. MS.DI doesn't contain a RegisterForDisposal method that you can call. You will have to create a separate wrapper that implements disposable to which you can hook your created instances. For instance:

services.AddSingleton<IBLogic, BLogic>();

services.AddScoped<DisposeWrapper>();
services.AddScoped<Func<string, IDBCache>>(p => (userId) =>
{
    var cache = new IDBCache(userId, p.GetRequiredService<IBLogic>());
    p.GetRequiredService<DisposeWrapper>().Add(cache);
    return cache;
});

Where the DisposeWrapper looks like this:

public sealed class DisposeWrapper : List<IDisposable>, IDisposable
{
    public void Dispose()
    {
        // Dispose in reverse order as creation.
        for (int i = this.Count - 1; i >= 0; i--) this[i].Dispose();
    }
}

Such implementation, however, comes with quite some caveats, because DisposeWrapper should be registered using the same scope as you want to cache your IDBCache. That's why in the example above, I registered both DisposeWrapper and the factory as Scoped.

Registering DisposeWrapper as Singleton, means that created IDBCache instances will only get disposed when the application ends. If you create many IDBCache instances during the lifetime of the application, it will cause a memory leak. This is unlikely a good idea in your scenario.

Registering both as transient, on the other hand, could still (confusingly) cause memory leaks, because the factory could be injected into a singleton, causing it (and its DisposeWrapper) to become a Captive Dependency.

Because of the complications of this design, I recommend changing your design.

If you redesign IDBCache in such way that runtime data isn't required during object construction, you can allow IDBCache to be registered in the container without factory (e.g. AddTransient<IDBCache>()) which allows it to be disposed normally by the container. This will remove all above-introduced complexity.

Upvotes: 3

Related Questions