Unity resolving cyclic dependency

2020-03-17 04:33发布

问题:

While learning Unity (DI framework in C#) I came across a situation where one class has a setter injection of ClassB

class ClassA : IClassA
{
    [Dependency]
    public IClassB ClassB
    {
        get { return _classB; }
        set
        {
            if (value == null) throw new ArgumentNullException("value");
            _classB = value;
        }
    }

and the other has a constructor injection of the ClassA

class ClassB : IClassB
{
    [InjectionConstructor]
    public ClassB(IClassA classA)
    {
        _classA = classA;
    }
}

I am not able to resolve both the classes correctly within the container.

var container = new UnityContainer();
container.RegisterType<IClassB, ClassB>();
container.RegisterType<IClassA, ClassA>();
IClassA classA = new ClassA();
var instance = container.Resolve<ClassA>();
instance.DoSomethingFromClassB();

log.Info("Constructor Injection");
var instanceB = container.Resolve<ClassB>();
instanceB.DoSomethingFromClassA();

This gives me a stack overflow exception

I tried different ordering of resolving this but it doesn't seem to work.

I this doable or am I just wasting my time.

What is exactly happening here ?

回答1:

The way that DI frameworks like Unity work is, when you call them to instantiate a class, they recursively instantiate all classes being passed into the constructor (or set by property) of that class. Those classes follow the same function, and so you can see how you've created an infinite loop of recursion. How does Unity construct A when it needs B and B when it needs A? Neither can ever be constructed.

You can't resolve co-dependent classes in most DI frameworks. This is a bad design pattern AKA a code smell. In the classical sense, if ClassA needs to know about ClassB, and ClassB in return needs to know about ClassA, then the reality is that they share concerns and should be combined into a single class ClassC. You gain nothing by having them in 2 separate classes since there is no separation of concerns in this case.

DI such as Unity is used to promote the pattern of Inversion of Control, which works only when classes have a one-way dependency (don't need to know about each other).



回答2:

I agree with @Haney that this is a code smell, but it is technically possible...

Just change one of the referenced types to be resolved via Lazy<T>. Then it does not actually resolve that type until it is used which will break out of the infinite recursion loop.

i.e.

class ClassB : IClassB
{
...
    [InjectionConstructor]
    public ClassB(Lazy<IClassA> classA)
    {
        _classA = classA;
    }
}