Question

Hi I'm trying to implement this extension method I've found in this article for simple injector since it doesn't support registration for an specific constructor out of the box.

according to the article I need to register the service with a fake delegate but since I'm using an open generic I can't do that.

so far this is the code that I've done:

 public sealed class ConstructorSelector : IConstructorSelector
    {
        public static readonly IConstructorSelector MostParameters =
            new ConstructorSelector(type => type.GetConstructors()
                .OrderByDescending(c => c.GetParameters().Length).First());

        public static readonly IConstructorSelector LeastParameters =
            new ConstructorSelector(type => type.GetConstructors()
                .OrderBy(c => c.GetParameters().Length).First());

        private readonly Func<Type, ConstructorInfo> _selector;

        public ConstructorSelector(Func<Type, ConstructorInfo> selector)
        {
            _selector = selector;
        }

        public ConstructorInfo GetConstructor(Type type)
        {
            return _selector(type);
        }
    }

    public interface IConstructorSelector
    {
        ConstructorInfo GetConstructor(Type type);
    }

    public static class SimpleInjectorExtesions
    {
        public static void RegisterOpenGeneric(this Container container,
            Type serviceType, 
            Type implementationtype, IConstructorSelector selector)
        {   
            //TODO: register the service with a fake delegate   
            container.ExpressionBuilt += (sender, e) =>
            {
                if (e.RegisteredServiceType != serviceType) return;
                var ctor = selector.GetConstructor(implementationtype);

                var parameters =
                    from p in ctor.GetParameters()
                    select container.GetRegistration(p.ParameterType, true)
                                    .BuildExpression();

                e.Expression = Expression.New(ctor, parameters);
            };
        }       
    }
Was it helpful?

Solution

since it doesn't support registration for an specific constructor out of the box

Simple Injector doesn't support this for a good reason. You should prefer letting your components have a single public constructor. Having multiple constructors is considered to be an anti-pattern.

If for some reason you can't follow this advice, here are some alternatives.

A simple solution would be to create a new type inside your Composition Root that inherits from the open generic type and defines just one single constructor. You can register that type instead:

// new sub type
private sealed class SingleCtorOpenType<T> : OpenType<T>
{
    public SingleCtorOpenType(IDep1 dep1, IDep2 dep2) 
        : base(dep1, dep2) { }
}

// registration
container.RegisterOpenGeneric(
    typeof(IOpenType<>), 
    typeof(SingleCtorOpenType<>));

This is the most pragmatic solution if you're dealing with just one type. If you have many types that have multiple constructors, overriding the container's constructor resolution behavior would be better. You can write a custom IConstructorResolutionBehavior implementation for your open generic type:

public class SpecialConstructorResolutionBehavior
    : IConstructorResolutionBehavior
{
    private IConstructorResolutionBehavior original;

    public SpecialConstructorResolutionBehavior(
        IConstructorResolutionBehavior original)
    {
        this.original = original;
    }

    public ConstructorInfo GetConstructor(Type serviceType, 
        Type implementationType)
    {
        if (serviceType.IsGenericType &&
            serviceType.GetGenericTypeDefinition() == typeof(IOpenType<>))
        {
            // do alternative constructor selection here for open types.
            // For instance:
            return (
                from ctor in implementationType.GetConstructors()
                orderby ctor.GetParameters().Length descending
                select ctor)
               .First();
        }

        // fall back to default behavior
        return original.GetConstructor(serviceType, implementationType);
    }
}

You can register this as follows:

container.Options.ConstructorResolutionBehavior =
    new SpecialConstructorResolutionBehavior(
        container.Options.ConstructorResolutionBehavior);

UPDATE

If you want to supply an Expression to the extension method to force the selection of a specific constructor, you can create an extension method that excepts an Expression<Func<object>> extracts the NewExpression for you like this:

public static void RegisterOpenGeneric(this Container container, 
    Type openGenericServiceType,
    Type openGenericImplementation, 
    Expression<Func<object>> constructorSelector)
{
    var constructor = 
        ((NewExpression)constructorSelector.Body).Constructor;
}

You can call this extension method as follows:

container.RegisterOpenGeneric(typeof(IList<>), typeof(List<>),
    constructorSelector: () => new List<int>());

But now the trouble starts. The constructor passed in is from List<int>, but we should be able to get any List<T>, so you will have to convert that List<int>.ctor() to the constructor of the type you are resolving. As far as I know, .NET lacks convenient helper methods such as ConstructorInfo.GetGenericMethodDefinition() and ConstructorInfo.MakeGenericMethod() overloads, so you'll need to do some querying and fiddling to find the correct constructor.

It will probably be easiest if you integrate this model with the IConstructorResolutionBehavior extendability mechanism, since the RegisterOpenGeneric methods calls that interface.

So your solution could look like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using SimpleInjector;
using SimpleInjector.Advanced;
using SimpleInjector.Extensions;

public static class OpenGenericConstructorExtensions
{
    public static void EnableOpenGenericConstructorSelection(
        this ContainerOptions options)
    {
        var selectors = new List<Func<Type, ConstructorInfo>>();

        options.ConstructorResolutionBehavior = 
            new ConstructorResolutionBehavior(
                options.ConstructorResolutionBehavior, selectors);

        options.Container.SetItem("selectors", selectors);
    }

    public static void RegisterOpenGeneric(this Container container, 
        Type openGenericServiceType,
        Type openGenericImplementation, 
        Expression<Func<object>> constructorSelector)
    {
        var selectors = (List<Func<Type, ConstructorInfo>>)
            container.GetItem("selectors");

        if (selectors == null) throw new InvalidOperationException(
            "call Options.EnableOpenGenericConstructorSelection first.");

        var constructor = 
            ((NewExpression)constructorSelector.Body).Constructor;

        selectors.Add(type =>
        {
            if (type == openGenericImplementation || (type.IsGenericType && 
                type.GetGenericTypeDefinition() == openGenericImplementation))
            {
                return GetConstructorForType(type, constructor);
            }

            return null;
        });

        container.RegisterOpenGeneric(openGenericServiceType, 
            openGenericImplementation);
    }

    private static ConstructorInfo GetConstructorForType(
        Type closedImplementationType, 
        ConstructorInfo closedConstructor)
    {
        var parameters = closedConstructor.GetParameters();

        var constructors =
            from ctor in closedImplementationType.GetConstructors()
            where ctor.GetParameters().Length == parameters.Length
            let parameterPairs = ctor.GetParameters().Zip(parameters, (p1, p2) =>
                new { left = p1.ParameterType, right = p2.ParameterType })
            where parameterPairs.All(pair => pair.left == pair.right ||
                    (pair.left.IsGenericType && pair.right.IsGenericType &&
                    pair.left.GetGenericTypeDefinition() == pair.right.GetGenericTypeDefinition()))
            select ctor;

        return constructors.Single();
    }

    private sealed class ConstructorResolutionBehavior 
        : IConstructorResolutionBehavior
    {
        private readonly IConstructorResolutionBehavior original;
        private readonly List<Func<Type, ConstructorInfo>> constructorSelectors;

        public ConstructorResolutionBehavior(
            IConstructorResolutionBehavior original,
            List<Func<Type, ConstructorInfo>> constructorSelectors)
        {
            this.original = original;
            this.constructorSelectors = constructorSelectors;
        }

        public ConstructorInfo GetConstructor(Type serviceType, 
            Type implementationType)
        {
            var constructors =
                from selector in this.constructorSelectors
                let constructor = selector(implementationType)
                where constructor != null
                select constructor;

            return constructors.FirstOrDefault() ?? 
                this.original.GetConstructor(serviceType, 
                    implementationType);
        }
    }
}

Since a custom IConstructorResolutionBehavior implementation is needed, you will always have to call the EnableOpenGenericConstructorSelection which is defined in the example above. This is how the code can be used: var container = new Container();

var container = new Container();

container.Options.EnableOpenGenericConstructorSelection();

container.RegisterOpenGeneric(typeof(IList<>), typeof(List<>), 
    constructorSelector: () => new List<int>());

var list = container.GetInstance<IList<string>>();
var listOfObject = container.GetInstance<IList<object>>();
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top