When are .NET Core dependency injected instances disposed?
Asked Answered
P

1

106

ASP.NET Core uses extension methods on IServiceCollection to set up dependency injection, then when a type is needed it uses the appropriate method to create a new instance:

  • AddTransient<T> - adds a type that is created again each time it's requested.
  • AddScoped<T> - adds a type that is kept for the scope of the request.
  • AddSingleton<T> - adds a type when it's first requested and keeps hold of it.

I have types that implement IDisposable and that will cause problems if they aren't disposed - in each of those patterns when is Dispose actually called?

Is there anything I need to add (such as exception handling) to ensure that the instance is always disposed?

Puppy answered 28/11, 2016 at 12:18 Comment(0)
S
96

The resolved objects have the same life-time/dispose cycle as their container, that's unless you manually dispose the transient services in code using using statement or .Dispose() method.

In ASP.NET Core you get a scoped container that's instantiated per request and gets disposed at the end of the request. At this time, scoped and transient dependencies that were created by this container will get disposed too (that's if they implement IDisposable interface), which you can also see on the source code here.

public void Dispose()
{
    lock (ResolvedServices)
    {
        if (_disposeCalled)
        {
            return;
        }
        _disposeCalled = true;
        if (_transientDisposables != null)
        {
            foreach (var disposable in _transientDisposables)
            {
                disposable.Dispose();
            }

            _transientDisposables.Clear();
        }

        // PERF: We've enumerating the dictionary so that we don't allocate to enumerate.
        // .Values allocates a ValueCollection on the heap, enumerating the dictionary allocates
        // a struct enumerator
        foreach (var entry in ResolvedServices)
        {
            (entry.Value as IDisposable)?.Dispose();
        }

        ResolvedServices.Clear();
    }
}

Singletons get disposed when the parent container gets disposed, usually means when the application shuts down.

TL;DR: As long as you don't instantiate scoped/transient services during application startup (using app.ApplicationServices.GetService<T>()) and your services correctly implement Disposable interface (like pointed in MSDN) there is nothing you need to take care of.

The parent container is unavailable outside of Configure(IApplicationBuilder app) method unless you do some funky things to make it accessible outside (which you shouldn't anyways).

Of course, its encouraged to free up transient services as soon as possible, especially if they consume much resources.

Suckow answered 28/11, 2016 at 13:36 Comment(8)
Unrelated, but I'm kind of shocked to see lock being used on a publicly-visible property (ResolvedServices in this case), which I always thought was against the recommendations.Axon
@StevenRands: ResolvedServices is internal for the matterSuckow
@Suckow Yes, it is internal, but that's technically public to the rest of the types in the same assembly.Axon
@StevenRands I'm curious as to why you believe that using lock on a public property is problematic. I do not see any problem with it. Could you elaborate? :)Bowls
@Bowls Just a general recommendation I've seen reiterated many times over the years. See Jon Skeet's take, or this on Pluralsight.Axon
@Zero3: It's to reduce the likely-hood of deadlocking when another object locks on that public property. With private lock objects, only the class itself can access it, so its less likely to cause deadlocks by others (consumers of the class/object)Suckow
"Singletons get disposed when the parent container gets disposed, usually means when the application shuts down." This does not seem to be the case anymoreFirooc
@Mikhail: That's not exactly what the OP asked: "when a type is needed it uses the appropriate method to create a new instance". OP asks explicitly for what happens, when the container creates the instance, this means registrations like: s.AddSingleton<T, TImpl>() or .AddSingleton<T>(sp => ...) factory method. The example you linked is called "returning singleton instance", emphasis on instance, read: You pass an already initialized object to be returned when T is requested. Singleton instances do not get disposed that's correct, but singleton registrations doSuckow

© 2022 - 2024 — McMap. All rights reserved.