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

本文关键字:泛型 简单 注入器 注册 构造函数 | 更新日期: 2023-09-27 18:25:22

嗨,我正试图实现我在本文中为简单注入器找到的这个扩展方法,因为它不支持开箱即用的特定构造函数的注册。

根据这篇文章,我需要用一个假委托来注册服务,但由于我使用的是开放泛型,我无法做到这一点。

到目前为止,这是我所做的代码:

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

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

因为它不支持为特定构造函数out注册开箱即用

Simple Injector不支持这一点是有充分理由的。您应该更喜欢让您的组件有一个单独的公共构造函数。拥有多个构造函数被认为是一种反模式。

如果出于某种原因你不能遵循这个建议,这里有一些替代方案。

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

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

如果你只处理一种类型,这是最实用的解决方案。如果有许多类型具有多个构造函数,那么重写容器的构造函数解析行为会更好。您可以为您的开放泛型类型编写自定义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>>();