Search code examples
c#stack-overflow

Infinite recursion trying to resolve type in custom DI container


I have a issue with this container I'm using. It works fine when I register a service with it implementation. But when I'm registering just a implementation it goes to Stackoverflow Exception

cause it calls over and over the GetIntance method in the initial check for an registered type.

How I can solve this?

public class DIContainer
{
    private readonly Dictionary<Type, Func<object>> _registeredTyped = new Dictionary<Type, Func<objec>>();

    public void Register<I, C>()
    {
        _registeredTypes.Add(typeof(I), () => GetInstance(typeof(C)));
    }

    public void RegisterSinglenton<T>(T obj)
    {
        _registeredTypes.Add(typeof(T), () => obj);
    }

    public T Get<T>()
    {
        return (T)GetInstance(typeof(T));
    }

    public object GetInstance(Type type)
    {
        if (_registeredTypes.ContainsKey(type))
        {
            return _registeredTypes[type]();
        }
        var constructor = type.GetConstructors().OrderByDescending(c => c.GetParameters().Length).First();
        var args = constructor.GetParameters().Select(p => GetInstance(p.ParameterType)).ToArray();
        return Activator.CreateInstance(type, args);
    }
}

Solution

  • When you register a type, you add a Func delegate to your _registeredTypes dictionary:

    _registeredTypes.Add(typeof(I), () => GetInstance(typeof(C)));
    

    So if you take Func<Test> testFactory = _registeredTypes[typeof(Test)];, and then call it (testFactory();), it will make a call to GetInstance to actually obtain the instance.

    Now let's take a look at the part of GetInstance that looks this object up:

        if (_registeredTypes.ContainsKey(type))
        {
            return _registeredTypes[type]();
        }
    

    So your code is obtaining the factory from _registeredTypes using the key type, and then it calls the factory method to return the instance. The factory method calls GetInstance for the same type, which obtains the factory from _registeredTypes using the key type, and then it calls the factory method to return the instance. The factory method calls GetInstance for the same type, which obtains the factory from _registeredTypes using the key type, and then it calls... etc.

    As you can see, you'll forever go around in a loop of looking up the type's factory in the dictionary, calling the factory method, which in turn calls the very same GetInstance method, until the stack is full and your application crashes.

    To solve this you can separate the GetInstanceinto two methods: one for creating instances, one for resolving instances through the _registeredTypes dictionary:

    public object GetInstance(Type type)
    {
        if (_registeredTypes.ContainsKey(type))
        {
            return _registeredTypes[type]();
        }
        else
        {
            return null; // or throw an exception, etc.
        }
    }
    
    public object CreateInstance(Type type)
    {
        var constructor = type.GetConstructors().OrderByDescending(c => c.GetParameters().Length).First();
        var args = constructor.GetParameters().Select(p => GetInstance(p.ParameterType)).ToArray();
        return Activator.CreateInstance(type, args);
    }
    

    And then you can change your Register method to use CreateInstance:

    public void Register<I, C>()
    {
        _registeredTypes.Add(typeof(I), () => CreateInstance(typeof(C)));
    }
    

    Also, some advice: consider the scenario where TypeA requires TypeB requires TypeA. This will also lead to a StackOverflowException. As an improvement, you might want to detect this before it leads to a crash, and throw your own exception.