Search code examples
c#dependency-injectionautofacdryioc

Resolve parameters according to the parent tree that triggered the resolve?


I have code similar to this:

class A: IA { ... }
class B: IB {
    public B(IA a, ...) { ... }
    ...
}
class C1 {
    public C1(IA a, IB b, ...) { ... }
}
class C2 {
    public C2(IA a, IB b, ...) { ... }
}

What I want is only two instances of A -- one to go with C1 and one to go with C2. I want two instances of B. The instance of B passed to C1 should get the same instance of A created for C1. C2 and his parameters should have a different instance of A. How can I configure this scenario in Autofac? It looked like the Owned instance lifetime feature was supposed to handle this, but the posted example was only one layer deep, not two.

My real situation is much more complex. I have a dozen inheritors of IB and a half dozen C defs that take various combinations of the IB inheritors. I wanted to avoid using the named instances because that would bloat my bootstrapper significantly and make it difficult to maintain.

Secondary question: does DryIoc support this? I could be talked into switching.


Solution

  • You're looking for something called "instance per graph" lifetime scope. It is not supported natively by autofac. If you're not stuck with autofac you can use Castle Windsor as explained in this related/duplicate question.

    If you're looking for a solution with autofac itself, you can implement it with InstancePerMatchingLifetimeScope or InstancePerLifetimeScope.

    Here's how you configure your container

    private IContainer Build()
    {
        ContainerBuilder builder = new ContainerBuilder();
    
        builder.RegisterType<A>().As<IA>().InstancePerMatchingLifetimeScope("SomeTag");
        builder.RegisterType<B>().As<IB>().InstancePerMatchingLifetimeScope("SomeTag");
        builder.RegisterType<C1>();
    
        return builder.Build();
    }
    

    Here's how you use it

    [Test]
    public void PerGraphLifeStyle()
    {
        var container = Build();
    
        C1 c1;
        C1 c2;
        using (var scope = container.BeginLifetimeScope("SomeTag"))
        {
            c1 = scope.Resolve<C1>();
            Assert.AreSame(c1.A, c1.B.A);
        }
    
        using (var scope = container.BeginLifetimeScope("SomeTag"))
        {
            c2 = scope.Resolve<C1>();
            Assert.AreSame(c1.A, c1.B.A);
        }
    
        Assert.AreNotSame(c1.A, c2.A);
    }
    

    Dummy classes I created for testing are below

    internal interface IA
    {
    }
    
    class A : IA
    {
    
    }
    
    internal interface IB
    {
        IA A { get; set; }
    }
    
    class B : IB
    {
        public B(IA a)
        {
            A = a;
        }
    
        public IA A { get; set; }
    }
    class C1
    {
        public IA A { get; set; }
        public IB B { get; set; }
    
        public C1(IA a, IB b)
        {
            A = a;
            B = b;
        }
    }