Unity auto- factory с параметрами

Я пытаюсь выяснить правильный способ вставить auto- factory, который принимает параметры, или даже если это возможно с Unity.

Например, я знаю, что могу это сделать:

public class TestLog
{
     private Func<ILog> logFactory;

     public TestLog(Func<ILog> logFactory)
     {
          this.logFactory = logFactory;
     }
     public ILog CreateLog()
     {
         return logFactory();
     }
}

Container.RegisterType<ILog, Log>();
TestLog test = Container.Resolve<TestLog>();
ILog log = test.CreateLog();

Теперь, что я хотел бы сделать, это:

public class TestLog
{
     private Func<string, ILog> logFactory;

     public TestLog(Func<string, ILog> logFactory)
     {
          this.logFactory = logFactory;
     }
     public ILog CreateLog(string name)
     {
         return logFactory(name);
     }
}

Container.RegisterType<ILog, Log>();
TestLog test = Container.Resolve<TestLog>();
ILog log = test.CreateLog("Test Name");

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

Очевидно, я мог бы создать свой собственный factory, но я ищу элегантный способ сделать это в Unity и с минимальным кодом.

Ответы

Ответ 1

Жаль, что это один из тех раздражающих людей, которые отвечают на их собственные вопросы, но я понял это.

public class TestLog
{
    private Func<string, ILog> logFactory;

    public TestLog(Func<string, ILog> logFactory)
    {
         this.logFactory = logFactory;
    }
    public ILog CreateLog(string name)
    {
        return logFactory(name);
    }
}

Container.RegisterType<Func<string, ILog>>(
     new InjectionFactory(c => 
        new Func<string, ILog>(name => new Log(name))
     ));

TestLog test = Container.Resolve<TestLog>();
ILog log = test.CreateLog("Test Name");

Ответ 2

Ответ @TheCodeKing отлично работает, но в большинстве (возможно, все?) случаях можно сократить до следующих значений:

Container.RegisterInstance<Func<string, ILog>>(name => new Log(name));

(обратите внимание, что я использую RegisterInstance() вместо RegisterType())

Поскольку реализация Func<> уже является своего рода factory, обычно нет необходимости обертывать ее в InjectionFactory. Это гарантирует, что каждое разрешение Func<string, ILog> является новым экземпляром, и я не могу думать о сценарии, который требует этого.

Ответ 3

Если вы ищете полностью типизированный интерфейс factory (например, для документации XML и имен параметров), вы можете использовать NuGet пакет, который вы можете использовать, просто определив интерфейс для factory, а затем связав его с конкретным типом, который вы хотите создать.

Код живет в GitHub: https://github.com/PombeirP/FactoryGenerator

Ответ 4

Autofac параметризует экземпляр для обработки сценариев, которым необходим автозапуск с параметрами.

Хотя Unity не поддерживает это из коробки, возможно реализовать расширение, которое будет работать аналогично Autofac.

Бесстыдный штекер: я реализовал такое расширение - Parameterized Auto Factory.
Его можно использовать таким образом, как это:

public class Log
{
    public void Info(string info) { /* ... */ }
    public void Warn(string info) { /* ... */ }
    public void Error(string info) { /* ... */ }
}

public class LogConsumer
{
    private readonly Log _log;
    private readonly string _consumerName;

    public LogConsumer(Log log, string consumerName)
    {
        _log = log;
        _consumerName = consumerName;
    }

    public void Frobnicate()
        => _log.Info($"{nameof(Frobnicate)} called on {_consumerName}");
}

var container = new UnityContainer()
    .AddNewExtension<UnityParameterizedAutoFactoryExtension>();

var logConsumerFactory = container.Resolve<Func<string, LogConsumer>>();
var gadget = logConsumerFactory("Gadget");
gadget.Frobnicate();

Пожалуйста отметьте:

  • Func<string, LogConsumer> разрешен из container, но он нигде не зарегистрирован - он генерируется автоматически.
  • Func<string, LogConsumer> предоставляет только параметр LogConsumer string consumerName для конструктора LogConsumer. В результате параметр Log log разрешен из контейнера. Если Func<string, Log, LogConsumer> auto-factory выглядела так, как Func<string, Log, LogConsumer>, тогда все параметры конструктора LogConsumer были бы предоставлены через автозавод.

В основном расширение работает следующим образом:

  1. Он регистрирует так называемый BuilderStrategy in Unity.
  2. Эта стратегия вызывается всякий раз, когда Unity собирается создать экземпляр типа.
  3. Если тип, который нужно создать, не был зарегистрирован явно и является параметром Func с параметрами, расширение перехватывает процесс создания экземпляра.
  4. Теперь нам нужно только динамически создать Func данного типа, который разрешает его возвращаемый тип из контейнера и передает параметры Func в виде коллекции ResolverOverride в метод IUnityContainer.Resolve.