UnityContainer и внутренний конструктор

У меня есть класс с внутренним конструктором, и я хочу разрешить его из Unity (2.0).

public class MyClass {
    internal MyClass(IService service) {
    }
}

тогда я делаю

_container.Resolve<MyClass>();

когда я это делаю, у меня есть исключение

Exception is: InvalidOperationException - The type MyClass cannot be constructed. 

IService зарегистрирован, и единственная проблема заключается в том, что конструктор является внутренним. Я действительно хочу, чтобы этот класс был общедоступным, но я хочу, чтобы его можно было создать только через фабрику (в которой я фактически вызываю container.Resolve<MyClass>()).

Есть ли способ заставить Unity увидеть этот внутренний конструктор? Например, InternalsVisibleTo или что-то в этом роде?


person Shaddix    schedule 20.06.2011    source источник
comment
Почему вы не хотите сделать этот ctor общедоступным? Вы создаете повторно используемую библиотеку?   -  person Steven    schedule 20.06.2011


Ответы (4)


Я немного покопался в том, как можно расширить Unity для этой цели, и нашел кое-какую интересную информацию.

Во-первых, кажется, что Unity выбирает, какой конструктор использовать, внутренне разрешая IConstructorSelectorPolicy. В Unity входит public abstract class ConstructorSelectorPolicyBase<TInjectionConstructorMarkerAttribute>, который включает в себя этот драгоценный камень:

/// <summary>
/// Choose the constructor to call for the given type.
/// </summary>
/// <param name="context">Current build context</param>
/// <param name="resolverPolicyDestination">The <see cref='IPolicyList'/> to add any
/// generated resolver objects into.</param>
/// <returns>The chosen constructor.</returns>
public SelectedConstructor SelectConstructor(IBuilderContext context, IPolicyList resolverPolicyDestination)
{
    Type typeToConstruct = context.BuildKey.Type;
    ConstructorInfo ctor = FindInjectionConstructor(typeToConstruct) ?? FindLongestConstructor(typeToConstruct);
    if (ctor != null)
    {
        return CreateSelectedConstructor(context, resolverPolicyDestination, ctor);
    }
    return null;
}

FindInjectionConstructor и company являются private static методами в этом классе, которые в конечном итоге вызывают Type.GetConstructors (перегрузка без параметров, которая возвращает только public конструкторов). Это говорит мне о том, что если вы можете настроить Unity на использование собственной политики селектора конструктора, которая сможет выбрать любой конструктор, вы — золотой человек.

Существует хорошая документация о том, как создавать и использовать свои собственные расширения контейнеров, поэтому я полагаю, что вполне возможно создать собственное CustomConstructorSelectorPolicy, включающее соответствующие части DefaultUnityConstructorSelectorPolicy (который является производным от абстрактного базового класса и используется по умолчанию, если вы не зарегистрируете что-то еще) и ConstructorSelectorPolicyBase (прямое производное от этого, вероятно, не сработает, поскольку ключевые методы не virtual , но вы можете повторно использовать код).

Поэтому я бы сказал, что это выполнимо с умеренными трудностями, но конечный результат будет довольно «чистым» с инженерной точки зрения.

person Jon    schedule 20.06.2011
comment
Вот пример использования пользовательского IConstructorSelectorPolicy msdn. microsoft.com/en-us/library/dn178464(v=pandp.30).aspx - person altso; 07.11.2016

Unity будет смотреть только на общедоступные конструкторы, поэтому вам нужно сделать этот конструктор общедоступным.

Я действительно хочу, чтобы этот класс был общедоступным, но я хочу, чтобы его можно было создать только через фабрику.

В этом случае создайте фабрику:

public class MyClassFactory : IMyClassFactory
{
    private readonly IService service;

    public MyClassFactory(IService service)
    {
        this.service = service;
    }

    MyClass IMyClassFactory.CreateNew()
    {
        return new MyClass(this.service);
    }
}

И зарегистрируйтесь:

_container.Register<IMyClassFactory, MyClassFactory>();

И решить:

_container.Resolve<IMyClassFactory>().CreateNew();

Вы также можете использовать Unity InjectionFactory:

container.Register<MyClass>(new InjectionFactory(c => 
{   
    return new MyClass(c.Resolve<IService>());
})); 

Чтобы это работало, сборка, содержащая этот код, должна иметь возможность видеть внутреннюю часть сборки, содержащей файл MyClass. Другими словами, сборка MyClass должна быть помечена InternalsVisibleTo.

Что также будет работать, так это следующее:

public static class MyClassFactory
{
    public static MyClass CreateNew(IService service)
    {
        return new MyClass(service);
    }
}

container.Register<MyClass>(new InjectionFactory(c => 
{   
    return MyClassFactory.Create(c.Resolve<IService>());
})); 

Хотя вам не нужно делать конструктор общедоступным, это отличный способ запутать ваш код :-)

person Steven    schedule 20.06.2011
comment
Спасибо, это почти то, что я делаю :) Мне просто не нравится менять эту фабрику каждый раз, когда меняются зависимости MyClass. Итак, ищем способ сделать это напрямую через Unity как-то - person Shaddix; 20.06.2011

Просто сделайте класс внутренним, а конструктор общедоступным...

  1. Публичный интерфейс
  2. Класс внутренний
  3. Конструктор класса public.
person Razer    schedule 29.11.2011

Возможно, есть обходные пути/хаки, которые позволят вам сделать это с Unity 9, я не знаю, есть ли они), но в целом, если вы хотите, чтобы класс управлялся Unity (или любым контейнером IOC), он должен быть общедоступным с публичным конструктором.

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

person Phil Sandler    schedule 20.06.2011
comment
он должен быть общедоступным с общедоступным конструктором. - это неправильно, Unity прекрасно работает с внутренними классами с публичными конструкторами. Это тот случай, когда я хочу управлять своими внутренними зависимостями с помощью IoC. То, что я сделал, это абстрактная фабрика. И внутри него я хочу создать экземпляр MyClass. Я могу сделать новый MyClass(_container.Resolve‹IService›()), но не хочу делать это вручную. - person Shaddix; 20.06.2011