使用多个构造函数注册开放泛型的简单注入器

时间:2013-07-28 03:11:56

标签: c# dependency-injection ioc-container simple-injector

您好我正在尝试实现我在this article中为简单注入器找到的扩展方法,因为它不支持开箱即用的特定构造函数的注册。

根据文章,我需要使用假代理注册服务,但由于我使用的是开放式通用,我不能这样做。

到目前为止,这是我完成的代码:

 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);
            };
        }       
    }

1 个答案:

答案 0 :(得分:4)

  

因为它不支持注册特定的构造函数   的方框

Simple Injector不支持这个有充分理由。您应该更喜欢让组件具有单个公共构造函数。 multiple constructors is considered to be an anti-pattern

如果由于某种原因你不能遵循这个建议,这里有一些选择。

一个简单的解决方案是在Composition Root内创建一个继承自开放泛型类型的新类型,并定义一个单独的构造函数。您可以注册该类型:

// 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<>));

如果您只处理一种类型,这是最实用的解决方案。如果您有许多具有多个构造函数的类型,则覆盖容器的constructor resolution behavior会更好。您可以为开放泛型类型编写自定义IConstructorResolutionBehavior实现:

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);
    }
}

您可以按如下方式注册:

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

<强>更新

如果要为扩展方法提供Expression以强制选择特定构造函数,可以创建一个超出Expression<Func<object>>的扩展方法,为您提取NewExpression像这样:

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

您可以按如下方式调用此扩展方法:

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

但现在麻烦开始了。传入的构造函数来自List<int>,但我们应该能够获得任何List<T>,因此您必须将List<int>.ctor()转换为您要解析的类型的构造函数。据我所知,.NET缺少方便的辅助方法,如ConstructorInfo.GetGenericMethodDefinition()ConstructorInfo.MakeGenericMethod()重载,因此您需要进行一些查询和摆弄才能找到正确的构造函数。

如果将此模型与IConstructorResolutionBehavior扩展性机制集成,可能是最简单的,因为RegisterOpenGeneric方法调用该接口。

所以你的解决方案看起来像这样:

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);
        }
    }
}

由于需要自定义IConstructorResolutionBehavior实施,因此您必须始终调用上面示例中定义的EnableOpenGenericConstructorSelection。这是代码的使用方式:     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>>();