Search code examples
autofac

Understanding the concept of Lifetime scope in Autofac


I just learned Autofac and I have some problems to understand the Autofac lifetime scope. Please help review the code below.

using (var scope = container.BeginLifetimeScope())
{
    // Resolve services from a scope that is a child
    // of the root container.
    var service = scope.Resolve<IService>();

    // You can also create nested scopes...
    using (var unitOfWorkScope = scope.BeginLifetimeScope())
    {
        var anotherService = unitOfWorkScope.Resolve<IOther>();
    }
}

The documentation says: "Lifetime scopes are disposable and they track component disposal".

Does it mean the service is disposable and can be recycled by the GC after the statement using (var scope = container.BeginLifetimeScope()) is finished?

And so does it to the anotherService in the nested scope?

How can I test it?

Thanks.


Solution

  • I think you first have to understand that Autofac allows to register components with different lifetimes:

    • Transient lifetime, via InstancePerDependency, which means the container will create a new instance every time it's asked to resolve a component
    • Per lifetime scope, via InstancePerLifetimeScope, which means the container will resolve to the same instance of a component in a specific lifetime scope
    • Singleton, via SingleInstance. In this case, at most one instance of the component is created by the container

    So what does component disposal track mean?

    It means that every lifetime scope keeps track of the components it owns. Upon disposal of the lifetime scope, every owned component that is disposable - i.e. that implements IDisposable - will be disposed of.

    So when will my components be disposed?

    Going back to the first point, it depends which lifetime they've been registered with.

    • If the component is registered with the transient lifetime, all the instances will be disposed when the owning lifetime scope is disposed

    • If it's been registered as per-lifetime scope, then the one instance will be disposed when the owning lifetime scope is disposed

    • If the component has been registered as a singleton, the instance is owned by the root lifetime scope, and will only be disposed when that root lifetime scope is disposed

    Some supporting code

    public class TransientService : IDisposable
    {
        private static int _instanceCount = 0;
        private readonly int _instanceNumber;
    
        public TransientService()
        {
            _instanceCount++;
            _instanceNumber = _instanceCount;
    
            Console.WriteLine($"Just created TransientService #{_instanceNumber}");
        }
    
        public void Dispose()
        {
            Console.WriteLine($"Disposing TransientService #{_instanceNumber}");
        }
    }
    
    public class LifetimeScopeService : IDisposable
    {
        private static int _instanceCount = 0;
        private readonly int _instanceNumber;
    
        public LifetimeScopeService()
        {
            _instanceCount++;
            _instanceNumber = _instanceCount;
    
            Console.WriteLine($"Just created LifetimeScopeService #{_instanceNumber}");
        }
    
        public void Dispose()
        {
            Console.WriteLine($"Disposing LifetimeScopeService #{_instanceNumber}");
        }
    }
    
    public class SingletonService : IDisposable
    {
        private static int _instanceCount = 0;
        private readonly int _instanceNumber;
    
        public SingletonService()
        {
            _instanceCount++;
            _instanceNumber = _instanceCount;
    
            Console.WriteLine($"Just created SingletonService #{_instanceNumber}");
        }
    
        public void Dispose()
        {
            Console.WriteLine($"Disposing SingletonService #{_instanceNumber}");
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            var builder = new ContainerBuilder();
    
            builder
                .RegisterType<TransientService>()
                .AsSelf()
                .InstancePerDependency();
    
            builder
                .RegisterType<LifetimeScopeService>()
                .AsSelf()
                .InstancePerLifetimeScope();
    
            builder
                .RegisterType<SingletonService>()
                .AsSelf()
                .SingleInstance();
    
            using (var container = builder.Build())
            {
                Console.WriteLine("Created the root scope");
    
                var rootTransientService = container.Resolve<TransientService>();
                var rootLifetimeScopeService = container.Resolve<LifetimeScopeService>();
                var rootSingletonService = container.Resolve<SingletonService>();
    
                var rootTransientServiceTwo = container.Resolve<TransientService>();
                var rootLifetimeScopeServiceTwo = container.Resolve<LifetimeScopeService>();
                var rootSingletonServiceTwo = container.Resolve<SingletonService>();
    
                using (var outerLifetimeScope = container.BeginLifetimeScope())
                {
                    Console.WriteLine("Created the outer lifetime scope");
    
                    var outerTransientService = outerLifetimeScope.Resolve<TransientService>();
                    var outerLifetimeScopeService = outerLifetimeScope.Resolve<LifetimeScopeService>();
                    var outerSingletonService = outerLifetimeScope.Resolve<SingletonService>();
    
                    var outerTransientServiceTwo = outerLifetimeScope.Resolve<TransientService>();
                    var outerLifetimeScopeServiceTwo = outerLifetimeScope.Resolve<LifetimeScopeService>();
                    var outerSingletonServiceTwo = outerLifetimeScope.Resolve<SingletonService>();
    
                    using (var innerLifetimeScope = container.BeginLifetimeScope())
                    {
                        Console.WriteLine("Created the inner lifetime scope");
    
                        var innerTransientService = innerLifetimeScope.Resolve<TransientService>();
                        var innerLifetimeScopeService = innerLifetimeScope.Resolve<LifetimeScopeService>();
                        var innerSingletonService = innerLifetimeScope.Resolve<SingletonService>();
    
                        var innerTransientServiceTwo = innerLifetimeScope.Resolve<TransientService>();
                        var innerLifetimeScopeServiceTwo = innerLifetimeScope.Resolve<LifetimeScopeService>();
                        var innerSingletonServiceTwo = innerLifetimeScope.Resolve<SingletonService>();
                    }
    
                    Console.WriteLine("Disposed the inner lifetime scope");
                }
    
                Console.WriteLine("Disposed the outer lifetime scope");
            }
    
            Console.WriteLine("Disposed the root scope");
    
            Console.ReadLine();
        }
    }
    

    3 services, one for each lifetime Autofac supports. The program is very simple, we have the root lifetime scope, an outer lifetime scope and an inner lifetime scope.

    Each of these lifetime scopes resolve 2 instances of each service, so each service is resolved 6 times. Here's the output:

    Created the root scope
    
        Just created TransientService #1
        Just created LifetimeScopeService #1
        Just created SingletonService #1
        Just created TransientService #2
    
            Created the outer lifetime scope
    
                Just created TransientService #3
                Just created LifetimeScopeService #2
                Just created TransientService #4
    
                    Created the inner lifetime scope
    
                        Just created TransientService #5
                        Just created LifetimeScopeService #3
                        Just created TransientService #6
    
                        Disposing TransientService #6
                        Disposing LifetimeScopeService #3
                        Disposing TransientService #5
    
                    Disposed the inner lifetime scope
    
                Disposing TransientService #4
                Disposing LifetimeScopeService #2
                Disposing TransientService #3
    
            Disposed the outer lifetime scope
    
        Disposing TransientService #2
        Disposing SingletonService #1
        Disposing LifetimeScopeService #1
        Disposing TransientService #1
    
    Disposed the root scope
    

    A few observations, always keeping in mind that all services have been resolved 6 times by 3 different lifetime scopes

    • We ended up with 6 instances of the TransientService, which matches the registration we made against the container. Disposal-wise, each lifetime scope disposed their 2 instances when themselves being disposed.

    • Only 3 instances of LifetimeScopeService were created. While each lifetime scope resolved this service twice, Autofac always returned, within the same lifetime scope, the same instance the second time it was resolved. Each instance got disposed by the owning lifetime scope.

    • There has only been 1 instance of SingletonService throughout the whole application. No lifetime scope boundary here since the 3 lifetime scopes resolved to the same instance of the service, which got disposed when the root scope was disposed.

    Edit: indented the output to make it more readable and make the hierarchy clearer