Search code examples
c#dependency-injectionioc-containersimple-injectorcachemanager

Registering generic type with multiple constructors


I am trying to move from Unity to Simple Injector and having trouble getting the Injection to work with Simple Injector.

Working Unity code is below

var container = new UnityContainer();
DependencyResolver.SetResolver(new UnityDependencyResolver(container));

container.RegisterType(
    typeof(ICacheManager<>),
    new ContainerControlledLifetimeManager(),
    new InjectionFactory((c, targetType, name) =>
        CacheFactory.FromConfiguration(targetType.GenericTypeArguments[0], "myCache")));

My Attempts:

var registration = Lifestyle.Singleton.CreateRegistration(
    typeof(ICacheManager<>),
    typeof(BaseCacheManager<>), 
    container);

container.AddRegistration(
    serviceType: typeof(BaseCacheManager<>),
    registration: registration);

I am getting the following error

For the container to be able to create BaseCacheManager<Object> it should have only one public constructor


Solution

  • There are several ways you can solve this in Simple Injector.

    First of all, always let your components have a single constructor, since having multiple constructors is an anti-pattern.

    Since however the supplied type is from an external library, it is impossible for you to change it.

    What you can do instead is derive from this particular type and create a sub class that only has one constructor. This type can call into the specific constructor of the base class you are interested in:

    class MyBaseClassManager<T> : BaseCacheManager<T>
    {
        public MyBaseClassManager([args]) : base([args]) { }
    }
    
    container.RegisterSingleton(typeof(ICacheManager<>), typeof(MyBaseCacheManager<>)):
    

    In general however, you should prevent having your application code depend on abstractions that are not defined by the application, since this is a Dependency Inversion Principle (DIP) violation. The DIP guides us into defining application-tailored abstractions. Based on such abstraction you can define an adapter that forwards the call to the external component. Example:

    // In the application's core layer
    public interface ICache<T>
    {
    }
    
    // Adapter in the Composition Root
    public sealed class CacheManagerCacheAdapter<T> : ICache<T>
    {
        private static BaseCacheManager<T> manager = new BaseCacheManager<T>();
    
        // Implement ICache<T> methods
        public object GetByKey(object key)
        {
            // translate and forward to the external component
            return this.manager[key];
        }
    }
    
    // Registration
    container.RegisterSingleton(typeof(ICache<>), typeof(CacheManagerCacheAdapter<>)):
    

    If the number of closed types you need is limited, you can also register every closed version explicitly:

    container.RegisterSingleton<ICacheManager<Foo>>(new BaseCacheManager<Foo>());    
    container.RegisterSingleton<ICacheManager<Bar>>(new BaseCacheManager<Bar>());    
    container.RegisterSingleton<ICacheManager<FooBar>>(new BaseCacheManager<FooBar>());    
    

    Yet another option is to override Constructor Resolution Behavior, as explained here. You can do this

    public class CacheManagerConstructorResolutionBehavior 
        : IConstructorResolutionBehavior {
        private readonly IConstructorResolutionBehavior org;
        public CacheManagerConstructorResolutionBehavior(IConstructorResolutionBehavior org) {
            this.org = org;
        }
        public ConstructorInfo GetConstructor(Type serviceType, Type implementationType) {
            if (implementationType.IsGenericType && 
                implementationType.GetGenericTypeDefinition() == typeof(BaseCacheManager<>)) {
                return implementationType.GetConstructors()
                    .OrderByDescending(c => c.GetParameters().Length)
                    .First();
            }
            return this.org.GetConstructor(serviceType, implementationType);
        }
    }
    
    var container = new Container();
    container.Options.ConstructorResolutionBehavior =
        new CacheManagerConstructorResolutionBehavior(
            container.Options.ConstructorResolutionBehavior);
    

    Yet another option is to hook onto the ResolveUnregisteredType` event although I would only advise this as last resort.