Search code examples
c#autofac

What Autofac LifetimeScope is assumed when registering a delegate?


I haven't been able to determine this from the documentation.

Given a registration:

builder.RegisterType<ExampleComponent>().As<IComponent>().InstancePerLifetimeScope();

What LifetimeScope will be assumed by the following registration?

builder.Register(ctx =>
{
    var component = ctx.Resolve<IComponent>();
    return new SomeService(component);
}).As<ISomeService>();

Note: This is just an example. Obviously in this case you'd just resolve ISomeService and allow Autofac to instantiate an instance of SomeService with the IComponent dependency. I have a more complex registration need for which this approach is necessary, but whose details aren't really pertinent to the question.

Given IComponent is registered as InstancePerLifetimeScope (which I understand to mean that it inherits the scope used to resolve this directly or the scope used to resolve a component for which this is a dependency), and that the delegate registered for ISomeService will be the default lifetime scope of InstancePerDependency, I would expect that the resolve of IComponent within the delegate would be with lifetime scope InstancePerDependency.

Is this correct?


Solution

  • When you resolve ISomeService, this will be done per dependency as expected, which means that you will get a new instance of SomeService on every resolve (by calling the delegate).

    However, the call to get component eg:

    var component = ctx.Resolve<IComponent>();
    

    Will return one instance of component shared per lifetime scope (so if you do not create any child/nested lifetime scope(s), it will be more or less a singleton).

    Here is a small example to demonstrate it.

    First here is a simple implementation for IComponent:

    public interface IComponent
    {
        int GetId();
    }
    
    public class ExampleComponent : IComponent
    {
        private static int id = 0;
        private int instanceId;
    
        public ExampleComponent()
        {
            this.instanceId = id;
            id++;
        }
    
        public int GetId()
        {
            return this.instanceId;
        }
    }
    

    Then for ISomeService :

    public interface ISomeService
    {
        int GetServiceID();
        int GetComponentId();
    }
    
    public class SomeService : ISomeService
    {
        private static int id = 0;
        private int instanceId;
    
    
        private readonly IComponent component;
    
        public SomeService(IComponent component)
        {
            if (component == null)
                throw new ArgumentNullException("component");
    
            this.component = component;
            this.instanceId = id;
            id++;
        }
    
        public int GetComponentId()
        {
            return this.component.GetId();
        }
    
        public int GetServiceID()
        {
            return this.instanceId;
        }
    }
    

    I kept registration as you wrote:

        ContainerBuilder builder = new ContainerBuilder();
    
             builder.RegisterType<ExampleComponent>().As<IComponent>().InstancePerLifetimeScope();
             builder.Register(ctx =>
             {
                 var component = ctx.Resolve<IComponent>();
                 return new SomeService(component);
             }).As<ISomeService>();
    
             IContainer rootContainer = builder.Build();
    

    And now let's do some resolve :

    ISomeService service1 = rootContainer.Resolve<ISomeService>();
             ISomeService service2 = rootContainer.Resolve<ISomeService>();
    
             Console.WriteLine(string.Format("Service 1: {0} , Component : {1} ", service1.GetServiceID(), service1.GetComponentId()));
             Console.WriteLine(string.Format("Service 2: {0} , Component : {1} ", service2.GetServiceID(), service2.GetComponentId()));
    

    You will get:

    Service 1: 0 , Component : 0 Service 2: 1 , Component : 0

    Since Component is shared by the main lifetime scope.

    Now of course, if you create child scopes:

    IContainer rootContainer = builder.Build();
    
            ILifetimeScope scope1 = rootContainer.BeginLifetimeScope();
            ILifetimeScope scope2 = rootContainer.BeginLifetimeScope();
    
            ISomeService service1 = scope1.Resolve<ISomeService>();
            ISomeService service2 = scope2.Resolve<ISomeService>();
    
            Console.WriteLine(string.Format("Service 1: {0} , Component : {1} ", service1.GetServiceID(), service1.GetComponentId()));
            Console.WriteLine(string.Format("Service 2: {0} , Component : {1} ", service2.GetServiceID(), service2.GetComponentId()));
    

    Service 1: 0 , Component : 0 Service 2: 1 , Component : 1

    In that case, you have 2 different scopes, so each resolve to component will provide a different one.