Подтвердить что ты не робот

Microsoft Unity. Как указать определенный параметр в конструкторе?

Я использую 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 имеет два параметра (не один параметр, как в предыдущем примере) в своем конструкторе (например, ISomeService1 и ISomeService2). Он отлично работает, когда я использую следующий код: container.RegisterType<ICustomerService, CustomerService>(new TransientLifetimeManager(), new InjectionConstructor(new SomeService1(), new SomeService2()));

Проблема заключается в том, что я не хочу указывать SomeService2() для второго параметра. Я хочу указать только первый параметр - SomeService1(). Но я получаю ошибку, которую мне нужно указать ни один, ни оба параметра.

Как я могу указать только первый параметр конструктора?

4b9b3361

Ответ 1

Лучший ответ - фактически использовать контейнер.

То, что вы делаете, говорит: "При создании этого типа используйте этот конкретный экземпляр объекта". Это не позволяет использовать возможности контейнера для создания экземпляра для вас. Вместо этого вы должны зарегистрировать 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 the default
    new InjectionConstructor(new ResolvedParameter<IService1>(),
        new ResolvedParameter<IService2>()));

Это сообщает контейнеру: при разрешении ICustomerService создайте экземпляр CustomerService, используя конструктор, который принимает IService1 и IService2. Чтобы получить эти параметры, перейдите в контейнер, чтобы разрешить эти типы.

Это немного подробный и обычный случай, поэтому есть несколько ярлыков. Прежде всего, вы можете передать объект Type вместо нового ResolvedParameter, например:

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

В качестве еще одного сокращения, если CustomerService имеет только один конструктор, или если тот, который вы хотите вызывать, является тем, который принимает самый большой список параметров, вы можете полностью исключить InjectionConstructor, так как тот конструктор, который контейнер будет выбирать в отсутствие другой конфигурации.

container.RegisterType<ICustomerService, CustomerService>();

Форма, которую вы используете, обычно используется, когда вы хотите, чтобы определенное значение передавалось для параметра конструктора, а не для разрешения службы обратно через контейнер.

Чтобы ответить на ваш первоначальный вопрос - ну, вы не можете делать то, что вы сказали. Параметру конструктора требуется значение какого-либо типа. Вы могли бы разместить что-нибудь еще там, где хотите, хотя, как правило, работает null.

Обратите внимание, что вы также можете смешивать две формы. Например, если вы хотите разрешить IService1 и передать значение null для параметра IService2, сделайте следующее:

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

Это должно делать то, что вам нужно.

Ответ 2

В качестве альтернативы ответа Криса Тавареса вы можете разрешить контейнеру разрешить только второй параметр:

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

Ответ 3

Крис Таварес дал хороший ответ с большим количеством информации.

Если у вас есть много параметров для ввода, то обычно это интерфейсы или экземпляры, которые могут быть разрешены Unity (используя методы differnet). Но что, если вы хотите предоставить только один параметр, который не может быть автоматически разрешен, например. строка для имени файла?

Теперь вам нужно предоставить все 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)
    ));

Ответ 4

Можно использовать иерархию контейнеров. Зарегистрируйте общую реализацию в родительском контейнере, этот экземпляр разрешит, если разрешен через мастер-контейнер. Затем создайте дочерний контейнер и зарегистрируйте альтернативную реализацию в дочернем контейнере. Эта реализация будет разрешена. Если разрешено с помощью дочернего контейнера, то есть регистрация в дочернем контейнере отменяет регистрацию в родительском контейнере.

Вот пример:

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. Чем меньше вы знаете, тем лучше.

Ответ 5

Вы можете указать второй параметр для конструктора (например, =null) или предложить один конструктор параметров в дополнение к двум конструкторам параметров, перегрузив его.