3

Consider situation when you need to create some recursively nested things, for example like that:

    public interface IRecurrentTestNodeFactory
    {
        RecurrentTestNode Create(int num);
    }

    public class RecurrentTestNode
    {
        public int Num { get; set; }
        public RecurrentTestNode Child { get; set; }

        public RecurrentTestNode(int num, IRecurrentTestNodeFactory factory)
        {
            Num = num;
            Child = num > 0 ? factory.Create(num - 1) : null;
        }
    }

Obvious implementation is like that:

    public class ManualRecurrentTestNodeFactory : IRecurrentTestNodeFactory
    {
        public RecurrentTestNode Create(int num)
        {
            return new RecurrentTestNode(num, this);
        }
    }

    [Test]
    public void ManualRecurrentTest()
    {
        var root = new ManualRecurrentTestNodeFactory().Create(1);
        Assert.NotNull(root);
        Assert.AreEqual(1, root.Num);
        Assert.NotNull(root.Child);
        Assert.AreEqual(0, root.Child.Num);
        Assert.Null(root.Child.Child);
    }

This test passes. But if you'll try to do the same thing with Windsor's Typed Factory Facility like that:

    [Test]
    public void RecurrentTest()
    {
        var windsor = new WindsorContainer();
        windsor.Kernel.AddFacility<TypedFactoryFacility>();
        windsor.Register(Component.For<IRecurrentTestNodeFactory>().AsFactory());
        windsor.Register(Component.For<RecurrentTestNode>().LifeStyle.Transient);

        var f = windsor.Resolve<IRecurrentTestNodeFactory>();
        var root = f.Create(1);
        Assert.NotNull(root);
        Assert.AreEqual(1, root.Num);
        Assert.NotNull(root.Child);
        Assert.AreEqual(0, root.Child.Num);
        Assert.Null(root.Child.Child);
    }

It is failing with this exceptions:

Castle.MicroKernel.ComponentActivator.ComponentActivatorException : ComponentActivator: could not instantiate Tests.RecurrentTestNode
  ----> System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation.
  ----> Castle.MicroKernel.CircularDependencyException : Dependency cycle has been detected when trying to resolve component 'Tests.RecurrentTestNode'.
The resolution tree that resulted in the cycle is the following:
Component 'Tests.RecurrentTestNode' resolved as dependency of
    component 'Tests.RecurrentTestNode' which is the root component being resolved.

It's obvious why such code could fail in case of services, but for factory it seems unnecessary restrictive. I'd like to left in factory variant because instead of plain int I have a bunch of container-resolved dependencies there.

Cybermaxs
  • 24,378
  • 8
  • 83
  • 112
Ivan Danilov
  • 14,287
  • 6
  • 48
  • 66
  • I agree, it does seem unnecessary restrictive, given that the RecurrentTestNode type is registered as having a transitive lifestyle. – Andrew Shepherd Sep 26 '12 at 03:23

1 Answers1

2

Do it lazily to break the cycle, not in the constructor. Windsor's behaviour is correct.

Krzysztof Kozmic
  • 27,267
  • 12
  • 73
  • 115
  • There's no cycle. I want two different instances there and I set `LifeStyle.Transient` for that. – Ivan Danilov Sep 27 '12 at 00:38
  • while creating one instance of the component you're asking for another instance, which will ask for another instance, which will ask for another instance... Windsor can't see if you've got any sort of defence mechanism around that. – Krzysztof Kozmic Sep 27 '12 at 03:13
  • Well, if it is endless - StackOverflowException will be thrown. In any case, with `Transient` there's no circular dependency for sure. Also, such limitation basically forces me to write factory myself by hand. My concrete situation is that I have factory creating VMs for UI tree nodes. It is naturally recursive. – Ivan Danilov Sep 27 '12 at 11:22
  • OK, there's obviously no sense to argue with the author how things should be :). Is there any means to change that behavior/restriction, so that it will not check circularity in case of transient dependency? – Ivan Danilov Sep 28 '12 at 00:36
  • there's no *easy* way to change that. I'm not sure if with enough force you can't bend it to do what you want, but this would require probably replacing some major internals. – Krzysztof Kozmic Sep 28 '12 at 03:20
  • Well, I suppose negative answer also counts as answer :). Thanks. – Ivan Danilov Sep 28 '12 at 11:36