1

I'm playing with .net 5 dependency injection, which is very useful. From various sources, like the accepted answer of this question Dependency injection, inject with parameters , I learn that using a factory method in AddTransient/Scoped/Singleton, the container will not dispose the object created, even if the object implements IDisposable. But the following code tells me clearly that the objects are disposed automatically (which is great, but I'd like to understand the story behind).

using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace ConsoleDI.Example
{
    public class TransientDisposable : IDisposable
    {
        public void Dispose() => Console.WriteLine($"{nameof(TransientDisposable)}.Dispose()");
    }

    public class ScopedDisposable : IDisposable
    {
        public void Dispose() => Console.WriteLine($"{nameof(ScopedDisposable)}.Dispose()");
    }

    public class SingletonDisposable : IDisposable
    {
        public void Dispose() => Console.WriteLine($"{nameof(SingletonDisposable)}.Dispose()");
    }

    class Program
    {
        static void Main(string[] args)
        {
            using IHost host = CreateHostBuilder(args).Build();

            ExemplifyDisposableScoping(host.Services, "Scope 1");
            Console.WriteLine();

            ExemplifyDisposableScoping(host.Services, "Scope 2");
            Console.WriteLine();

            host.Run();
        }

        static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices((_, services) =>
                    services.AddTransient<TransientDisposable>(_ => new TransientDisposable())
                            .AddScoped<ScopedDisposable>(_ => new ScopedDisposable())
                            .AddSingleton<SingletonDisposable>(_ => new SingletonDisposable())
                );

        static void ExemplifyDisposableScoping(IServiceProvider services, string scope)
        {
            Console.WriteLine($"{scope}...");

            using IServiceScope serviceScope = services.CreateScope();
            IServiceProvider provider = serviceScope.ServiceProvider;

            _ = provider.GetRequiredService<TransientDisposable>();
            _ = provider.GetRequiredService<ScopedDisposable>();
            _ = provider.GetRequiredService<SingletonDisposable>();
        }
    }
}

output:

Scope 1...
ScopedDisposable.Dispose()
TransientDisposable.Dispose()

Scope 2...
ScopedDisposable.Dispose()
TransientDisposable.Dispose()

info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /home/zhjun/Work/tmp/ConsoleDI.Example
^Cinfo: Microsoft.Hosting.Lifetime[0]
      Application is shutting down...
SingletonDisposable.Dispose()

So why is this? Is this a .net 5 DI container improvement? or is this always this way?

Ralph Zhang
  • 5,015
  • 5
  • 30
  • 40

1 Answers1

3

I wouldn't say that the selected answer is incorrect, but at the very least it is incomplete, because the only registration that does not cause instances to be disposed is the AddSingleton<T>(T) call. Everything else (including instances returned from registered factory delegates for transient, scoped, and singleton) will be disposed of. This behavior is 'by design', specified in MS.DI's unit tests, and has been this way since version 1.0. The official Microsoft documentation is quite clear about this:

In the following example, the services are created by the service container and disposed automatically:

services.AddScoped<Service1>();
services.AddSingleton<Service2>();
services.AddSingleton<IService3>(sp => new Service3());

In the following example: [...] The framework doesn't dispose of the services automatically.

services.AddSingleton<Service1>(new Service1());
services.AddSingleton(new Service2());

The central idea behind this is that with AddSingleton<T>(T) you provide an already created instance or, in the terminology of Autofac, 'externally owned'. The supplied instance already lives before the container exists and it might be the user's goal to keep using that object long after the container was disposed of, which is why such instance is not disposed of at that point.

Steven
  • 166,672
  • 24
  • 332
  • 435