Support detection of circular dependencies in dynamic factory-based registrations
As noted in an ignored integration test: BoDi.Tests.RegisterFactoryDelegateTests.ShouldThrowExceptionForDynamicCircuarDepenencies - if a factory delegate registers a component and makes use of a resolved IObjectContainer inside of the registration, circular dependencies are not detected if they exist in the resolved objects.
Example
Here's the example from that integration test:
container.RegisterFactoryAs<ClassWithCircularDependency1>(c => new ClassWithCircularDependency1(c.Resolve<ClassWithCircularDependency2>()));
container.Resolve<ClassWithCircularDependency1>();
Here, we have two 'resolutions' occurring. An outer resolution - in which the container resolves a factory registration for ClassWithCircularDependency1 and an inner resolution in which the container resolves an unregistered type for ClassWithCircularDependency2.
Root cause analysis
The root cause of this is that when resolving an IObjectContainer and using this to dynamically resolve dependencies, that inner resolve operation does not have access to the "resolution path" information of the outer resolution operation. This thwarts circular dependency detection because the resolution path chain is repeatedly broken and started again from fresh, and never comes full circle.
The resolution path looks like:
- We begin with the outer resolve operation's resolution path:
- ClassWithCircularDependency1
- IObjectContainer
- Then a new resolve operation - the inner one - begins a new resolution path
- ClassWithCircularDependency2
- ClassWithCircularDependency1
- IObjectContainer
- Another new inner resolve operation begins, using a new path, and another, and another until the stack overflows
Proposed solution (overview)
I have a solution for this - which is that when resolving an IObjectContainer and the object container is not the very first thing in the resolution path, then instead of resolving and returning the real object container, instead return an instance of a proxy class which also implements the same interface (and contains the real container as a private field). That same proxy type would also receive the "resolution path" information from the parent/outer resolution operation.
Within the proxy type, most calls may just be proxied wholesale, but calls to resolve things would instead use a resolve method which passes on that resolution path information. Thus the continuity of the 'overall' resolve operation (the outer and the inner) is preserved. Thus there is only ever one resolution path in play and it looks like so:
- ClassWithCircularDependency1
- IObjectContainer
- ClassWithCircularDependency2
- ClassWithCircularDependency1
At this stage, circular dependency detection may trigger and raise an exception, because ClassWithCircularDependency1 appears twice in the path.
Where to find an implementation
As a project of my own, I have forked BoDi. I have fixed this very same problem in that project but it's diverged so much that it would be impossible to do a simple back-port of the fix into BoDI. Indeed, I've since archived that forked repo in order to create a new non-forked one.
However, you can find my fix in this this issue in that archived repo. The relevant commits should all be findable from the issue.
This makes sense. Let's come back to it when we have moved away from the single-file approach.