微软统一。如何在构造函数中指定某个参数?

Posted

技术标签:

【中文标题】微软统一。如何在构造函数中指定某个参数?【英文标题】:Microsoft Unity. How to specify a certain parameter in constructor? 【发布时间】:2011-05-02 21:06:02 【问题描述】:

我正在使用 Microsoft Unity。我有一个接口ICustomerService 和它的实现CustomerService。我可以使用以下代码为 Unity 容器注册它们:

container.RegisterType<ICustomerService, CustomerService>(new TransientLifetimeManager());

如果CustomerService在其构造函数中有某个参数(例如ISomeService1),我使用如下代码(我需要指定SomeService1):

container.RegisterType<ICustomerService, CustomerService>(new TransientLifetimeManager(), new InjectionConstructor(new SomeService1()));

这里没有问题。

CustomerService 类在其构造函数中有两个参数(不是上一个示例中的一个参数)时出现问题(例如ISomeService1ISomeService2)。当我使用以下代码时它工作正常: container.RegisterType&lt;ICustomerService, CustomerService&gt;(new TransientLifetimeManager(), new InjectionConstructor(new SomeService1(), new SomeService2()));

问题是我不想为第二个参数指定SomeService2()。我只想指定第一个参数 - SomeService1()。但是我得到了我需要指定一个或两个参数的错误。

如何只指定构造函数的第一个参数?

【问题讨论】:

嗨和.maz,你有没有得到任何不需要提供其他参数类型的解决方案。类似键值的东西,我们可以指定构造函数的名称和值 【参考方案1】:

Chris Tavares 给出了很好的答案,提供了很多信息。

如果您有许多要注入的参数,通常这些是可由 Unity 解析的接口或实例(使用不同的技术)。但是,如果您只想提供一个无法自动解析的参数怎么办,例如文件名的字符串?

现在您必须提供所有typeof(IMyProvider) 和一个字符串或实例。但老实说,Unity 可以只提供类型,因为 Unity 已经有了选择最佳 ctor 的策略。

所以我编写了 InjectionConstructor 的替代代码:

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Microsoft.Practices.ObjectBuilder2;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.ObjectBuilder;
using Microsoft.Practices.Unity.Utility;

namespace Microsoft.Practices.Unity

    /// <summary>
    /// A class that holds the collection of information for a constructor, 
    /// so that the container can be configured to call this constructor.
    /// This Class is similar to InjectionConstructor, but you need not provide
    /// all Parameters, just the ones you want to override or which cannot be resolved automatically
    /// The given params are used in given order if type matches
    /// </summary>
    public class InjectionConstructorRelaxed : InjectionMember
    
        private List<InjectionParameterValue> _parameterValues;

        /// <summary>
        /// Create a new instance of <see cref="InjectionConstructor"/> that looks
        /// for a constructor with the given set of parameters.
        /// </summary>
        /// <param name="parameterValues">The values for the parameters, that will
        /// be converted to <see cref="InjectionParameterValue"/> objects.</param>
        public InjectionConstructorRelaxed(params object[] parameterValues)
        
            _parameterValues = InjectionParameterValue.ToParameters(parameterValues).ToList();
        

        /// <summary>
        /// Add policies to the <paramref name="policies"/> to configure the
        /// container to call this constructor with the appropriate parameter values.
        /// </summary>
        /// <param name="serviceType">Interface registered, ignored in this implementation.</param>
        /// <param name="implementationType">Type to register.</param>
        /// <param name="name">Name used to resolve the type object.</param>
        /// <param name="policies">Policy list to add policies to.</param>
        public override void AddPolicies(Type serviceType, Type implementationType, string name, IPolicyList policies)
        
            ConstructorInfo ctor = FindExactMatchingConstructor(implementationType);
            if (ctor == null)
            
                //if exact matching ctor not found, use the longest one and try to adjust the parameters.
                //use given Params if type matches otherwise use the type to advise Unity to resolve later
                ctor = FindLongestConstructor(implementationType);
                if (ctor != null)
                
                    //adjust parameters
                    var newParams = new List<InjectionParameterValue>();
                    foreach (var parameter in ctor.GetParameters())
                    
                        var injectionParameterValue =
                            _parameterValues.FirstOrDefault(value => value.MatchesType(parameter.ParameterType));
                        if (injectionParameterValue != null)
                        
                            newParams.Add(injectionParameterValue);
                            _parameterValues.Remove(injectionParameterValue);
                        
                        else
                            newParams.Add(InjectionParameterValue.ToParameter(parameter.ParameterType));
                    
                    _parameterValues = newParams;
                
                else
                
                    throw new InvalidOperationException(
                        string.Format(
                            CultureInfo.CurrentCulture,
                            "No constructor found for type 0.",
                            implementationType.GetTypeInfo().Name));
                
            
            policies.Set<IConstructorSelectorPolicy>(
                new SpecifiedConstructorSelectorPolicy(ctor, _parameterValues.ToArray()),
                new NamedTypeBuildKey(implementationType, name));
        



        private ConstructorInfo FindExactMatchingConstructor(Type typeToCreate)
        
            var matcher = new ParameterMatcher(_parameterValues);
            var typeToCreateReflector = new ReflectionHelper(typeToCreate);

            foreach (ConstructorInfo ctor in typeToCreateReflector.InstanceConstructors)
            
                if (matcher.Matches(ctor.GetParameters()))
                
                    return ctor;
                
            

            return null;
        

       private static ConstructorInfo FindLongestConstructor(Type typeToConstruct)
        
            ReflectionHelper typeToConstructReflector = new ReflectionHelper(typeToConstruct);

            ConstructorInfo[] constructors = typeToConstructReflector.InstanceConstructors.ToArray();
            Array.Sort(constructors, new ConstructorLengthComparer());

            switch (constructors.Length)
            
                case 0:
                    return null;

                case 1:
                    return constructors[0];

                default:
                    int paramLength = constructors[0].GetParameters().Length;
                    if (constructors[1].GetParameters().Length == paramLength)
                    
                        throw new InvalidOperationException(
                            string.Format(
                                CultureInfo.CurrentCulture,
                                "The type 0 has multiple constructors of length 1. Unable to disambiguate.",
                                typeToConstruct.GetTypeInfo().Name,
                                paramLength));
                    
                    return constructors[0];
            
        
        private class ConstructorLengthComparer : IComparer<ConstructorInfo>
        
            /// <summary>
            /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other.
            /// </summary>
            /// <param name="y">The second object to compare.</param>
            /// <param name="x">The first object to compare.</param>
            /// <returns>
            /// Value Condition Less than zero is less than y. Zero equals y. Greater than zero is greater than y.
            /// </returns>
            [SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "Validation done by Guard class")]
            public int Compare(ConstructorInfo x, ConstructorInfo y)
            
                Guard.ArgumentNotNull(x, "x");
                Guard.ArgumentNotNull(y, "y");

                return y.GetParameters().Length - x.GetParameters().Length;
            
        
    

用法:

container.RegisterType(new TransientLifetimeManager(), new InjectionConstructorRelaxed(
    new SomeService1("with special options")
    //, new SomeService2() //not needed, normal unity resolving used
    //equivalent to: , typeof(SomeService2)
    ));

【讨论】:

【参考方案2】:

您可以使用容器层次结构。在父容器中注册通用实现,如果通过主容器解析,此实例将解析。然后创建子容器,并在子容器中注册替代实现。如果通过子容器解决,此实现将解决,即子容器中的注册覆盖父容器中的注册。

示例如下:

public interface IService 

public interface IOtherService 

// Standard implementation of IService
public class StandardService : IService 

// Alternative implementaion of IService
public class SpecialService : IService 

public class OtherService : IOtherService 

public class Consumer

    public Consumer(IService service, IOtherService otherService)
    


private void Test()

    IUnityContainer parent = new UnityContainer()
        .RegisterType<IService, StandardService>()
        .RegisterType<IOtherService, OtherService>();

    // Here standardWay is initialized with StandardService as IService and OtherService as IOtherService
    Consumer standardWay = parent.Resolve<Consumer>();

    // We construct child container and override IService registration
    IUnityContainer child = parent.CreateChildContainer()
        .RegisterType<IService, SpecialService>();

    // And here specialWay is initialized with SpecialService as IService and still OtherService as IOtherService
    Consumer specialWay = child.Resolve<Consumer>();

    // Profit!

请注意,使用容器层次结构,您对构造函数中的参数数量一无所知,这很好,因为只要您绑定参数计数及其类型,您就无法使用 IoC 的全部功能。你知道的越少越好。

【讨论】:

【参考方案3】:

作为 Chris Tavares 回答的替代方案,您可以让容器仅解析第二个参数:

container.RegisterType<ICustomerService, CustomerService>(
    new InjectionConstructor(new SomeService1(), new ResolvedParameter<IService2>());

【讨论】:

【参考方案4】:

您的最佳答案是实际使用容器。

您所做的是说“在构建这种类型时,使用对象的这个特定实例。”这没有利用容器为您构建实例的能力。相反,您应该在容器中注册 IService1 和 IService2。然后,告诉容器为您解决这些依赖关系。

看起来像这样:

container.RegisterType<IService1, SomeService1>();
container.RegisterType<IService2, SomeService2>();

它的作用是告诉容器“只要存在 IService1 类型的依赖项,就新建一个 SomeService1 类型的新对象并将其交给它”,对于 IService2 也是如此。

接下来,您需要告诉容器如何处理 ICustomerService。一般来说,你会这样做:

container.RegisterType<ICustomerService, CustomerService>(
    // Note, don't need to explicitly say transient, that's the default
    new InjectionConstructor(new ResolvedParameter<IService1>(),
        new ResolvedParameter<IService2>()));

这告诉容器:当解析 ICustomerService 时,使用带有 IService1 和 IService2 的构造函数新建一个 CustomerService 实例。要获取这些参数,请回调容器以解析这些类型。

这有点冗长,而且是常见的情况,所以有一些捷径。首先,您可以传递一个 Type 对象而不是执行新的 ResolvedParameter,如下所示:

container.RegisterType<ICustomerService, CustomerService>(
    new InjectionConstructor(typeof(IService1), typeof (IService2)));

作为另一种简写,如果 CustomerService 只有一个构造函数,或者如果您要调用的构造函数是采用最大参数列表的构造函数,则可以完全不使用 InjectionConstructor,因为这是容器将在没有其他配置。

container.RegisterType<ICustomerService, CustomerService>();

当您希望为构造函数参数传递特定值而不是通过容器解析回服务时,通常使用您使用的表单。

要回答您最初的问题 - 好吧,您不能完全按照您所说的去做。构造函数参数需要某种值。不过,您可以在其中放置任何您想要的东西 - null 通常有效。

请注意,您也可以混合使用这两种形式。例如,如果要解析 IService1 并为 IService2 参数传递 null,请执行以下操作:

container.RegisterType<ICustomerService, CustomerService>(
    new InjectionConstructor(typeof(IService1), null));

* 编辑 *

根据下面的评论,您真正想要的是另一个功能 - 名为注册。

基本上,您有两个 IService1 实现和一个 IService2 实现。所以,你可以做的就是注册它们,然后告诉容器使用哪一个。

首先,要注册第二个实现,您需要给出一个明确的名称:

container.RegisterType<IService1, OtherService1Impl>("other");

然后您可以告诉容器解析 IService1 但使用名称。这是 ResolvedParameter 类型存在的主要原因。由于您只需要 IService2 的默认值,因此可以使用 typeof() 作为简写。您仍然需要为参数指定两种类型,但不需要特定值。如果这有任何意义。

container.RegisterType<ICustomerService, CustomerService>(
    new InjectionConstructor(new ResolvedParameter<IService1>("other"), typeof(IService2));

这应该可以满足您的需求。

【讨论】:

克里斯,谢谢。但问题是我有两个 IService1 的实现。我们将它们命名为 Service1Impl1 和 Service1Impl2。它们都用于解决方案。 CustomerManager 应该使用 Service1Impl1,而 OrderManager 应该使用 Service1Impl2。这就是为什么我需要将Service1Impl1 指定为CustomerService 的第一个参数。但我不想指定第二个参数,因为只有一个使用过的 IService2 实现,并且它已经使用以下源代码注册:container.RegisterType(); 我将编辑我的答案,它需要的细节比我可以在评论中容纳的更多。 这应该是选择的答案。谢谢克里斯。【参考方案5】:

您可以将第二个参数默认为构造函数(例如,=null),或者通过重载在双参数构造函数之外提供单参数构造函数。

【讨论】:

以上是关于微软统一。如何在构造函数中指定某个参数?的主要内容,如果未能解决你的问题,请参考以下文章

Java泛型方法和构造函数

Unity构造函数注入代码示例

运算符new的执行顺序和构造函数的参数

如何从派生类复制构造函数调用基类复制构造函数? [复制]

如何在python函数中指定传递的参数必须是函数[重复]

如何在函数中指定参数在 Swift 中可以为零