2016-12-29 2 views
2

В SimpleInjector documentation сказано:SimpleInjector неожиданное поведение явно зарегистрированного одноплодной коллекции

Простой Инжектор сохраняет образ жизни экземпляров, которые возвращаются из впрыскивается IEnumerable<T>, ICollection<T>, IList<T>, IReadOnlyCollection<T> и IReadOnlyList<T> экземпляра. На самом деле вы не должны видеть введенный IEnumerable<T> в качестве коллекции экземпляров; вы должны рассматривать это поток инстанций. Простой Injector всегда впрыснуть ссылку на тот же поток ( IEnumerable<T> или сам ICollection<T> одноэлементно) и каждый раз вы итерируете IEnumerable<T>, для каждого отдельного компонента, то контейнер просит разрешить пример, основанный на образе жизни этот компонент.

После прочтения этого я ожидал, что после того, как я зарегистрировал все IFoo и IFoo<T> как одиночки, IEnumerable<IFoo> будет одноточечно сам и перечисление всегда будет приводить к тем же объектам.

Но этот кусок кода не работает так:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Reflection; 
using SimpleInjector; 

class Program 
{ 
    interface IFoo{} 
    interface IFoo<T> : IFoo{} 
    class Foo : IFoo<Foo>{} 

    class Consumer1 
    { 
     public IFoo<Foo> FooInstance { get; } 
     public Consumer1(IFoo<Foo> fooInstance){FooInstance = fooInstance;} 
    } 

    class Consumer2 
    { 
     public IFoo<Foo> FooInstance { get; } 
     public Consumer2(IFoo<Foo> fooInstance){FooInstance = fooInstance;} 
    } 

    class Consumer3 
    { 
     public IEnumerable<IFoo> FooIntances { get; } 
     public Consumer3(IEnumerable<IFoo> fooIntances){FooIntances = fooIntances;} 
    } 

    static void Main(string[] args) 
    { 
     var cont = new Container(); 

     var types = Assembly.GetExecutingAssembly() 
          .GetTypes() 
          .Where(x => 
            typeof(IFoo<>).IsAssignableFrom(x) || 
            typeof(IFoo).IsAssignableFrom(x)) 
          .Where(x => !x.IsInterface); 

     foreach (var type in types) 
     { 
      var genericFoo = type.GetInterfaces().Single(x=>x.IsGenericType); 
      var genericArgs = genericFoo.GenericTypeArguments; 
      var closedGenericFoo = typeof(IFoo<>).MakeGenericType(genericArgs); 

      var reg = Lifestyle.Singleton.CreateRegistration(type, cont); 

      cont.AddRegistration(closedGenericFoo, reg); 
      cont.AddRegistration(typeof(IFoo), reg); 
     } 

     cont.RegisterCollection<IFoo>(new[] {Assembly.GetExecutingAssembly()}); 

     var cons1 = cont.GetInstance<Consumer1>(); 
     var cons2 = cont.GetInstance<Consumer2>(); 
     var cons3_1 = cont.GetInstance<Consumer3>(); 
     var cons3_2 = cont.GetInstance<Consumer3>(); 

     // Expected: true | Actually: true 
     Console.WriteLine($"cons1.FooInstance == cons2.FooInstance : {cons1.FooInstance == cons2.FooInstance}"); 
     // Expected: true | Actually: true 
     Console.WriteLine($"cons3_1.FooInstances == cons3_2.FooInstances : {cons3_1.FooIntances == cons3_2.FooIntances}"); 
     // Expected: true | Actually: false 
     Console.WriteLine($"cons3_1.FooIntances.First() == cons1.FooInstance : {cons3_1.FooIntances.First() == cons1.FooInstance}"); 
     // Expected: true | Actually: false 
     Console.WriteLine($"cons3_1.FooIntances.First() == cons3_2.FooIntances.First() : {cons3_1.FooIntances.First() == cons3_2.FooIntances.First()}"); 

     Console.ReadKey(); 
    } 
} 

То, что я пытаюсь достичь:

  1. одного Foo реализации зарегистрирована как IFoo<Foo> и IFoo (частично работ)
  2. одиночный поток IFoo который IEnumerable<IFoo> (он работает)
  3. Итерация из IEnumerable выше должен дать мне тот же Foo реализации (не работает)

Возможно ли это?

+0

Пожалуйста, позвоните 'Container.Verify()' в конце фазы регистрации. Это приведет к исключению, которое объясняет, почему ваши ожидания не выполняются. – Steven

+0

@Steven это не так :) Он сказал, что у меня есть 3 регистрации для 'Foo': 1) Singleton' IFoo', 2) Singleton 'IFoo ' и 3) Transient 'IFoo'. Третий вышел из ниоткуда. Это объясняет, почему 'IEnumerable ' каждый раз создает новый 'IFoo', но не объясняет, как я могу избежать временной регистрации' IFoo' – Szer

ответ

2

То, что здесь происходит следующее:

Призыва к cont.RegisterCollection<IFoo>(new[] {Assembly.GetExecutingAssembly()}) приводит к следующей регистрации:

cont.RegisterCollection<IFoo>(new[] { typeof(Foo) }); 

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

С вашей регистрацией однако регистрация для Foo может быть найдена. Однако есть регистрационные данные для IFoo<Foo> и IFoo, но что такое простой инжектор, это разные регистрации.

В результате этого Simple Injector создаст регистрацию в последнюю минуту для Foo от вашего имени, а стиль жизни по умолчанию, используемый Simple Injector, - Transient.

Вот почему, хотя вы уже зарегистрировали Foo дважды (один раз как IFoo<Foo> и один раз как IFoo), элемент коллекции будет оставаться временным.

Вы бы заметили эту проблему, если в конце процесса регистрации вы вызвали Container.Verify(). Простой инжектор обнаруживает эти неправильные конфигурации для вас. Вы всегда должны звонить Container.Verify().

Чтобы решить эту проблему, вы можете изменить конфигурацию на следующее:

static void Main(string[] args) 
{ 
    var container = new Container(); 

    // GetTypesToRegister can do the assembly scanning for you. 
    IEnumerable<Type> types = container.GetTypesToRegister(typeof(IFoo<>), 
     new[] { Assembly.GetExecutingAssembly() }); 

    // Here we create a list of Registration instance once. 
    Registration[] regs = (
     from type in types 
     select Lifestyle.Singleton.CreateRegistration(type, container)) 
     .ToArray(); 

    // Here we register the registrations as one-to-one mapping. 
    foreach (var reg in regs) 
    { 
     Type closedGenericFoo = reg.ImplementationType.GetInterfaces() 
      .Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IFoo<>)) 
      .Single(); 

     container.AddRegistration(closedGenericFoo, reg); 
    } 

    // Here we make a one-to-many mapping between IFoo and the registrations. 
    container.RegisterCollection<IFoo>(regs); 

    container.Verify(); 

    ... 
} 
+0

Это отличный ответ. Однако я не могу использовать 'Verify()' в моем реальном проекте из-за 'Akka.Net'. После того, как я зарегистрирую всех участников в контейнере 'Verify()' throws error, потому что он пытается создать экземпляры актеров, а 'Akka.Net' просто не позволяет создавать участников вне рамки. Но это уже другая история. – Szer

+0

@Szer: Если Akka.Net контролирует создание актеров, а не Simple Injector, вы должны * не * регистрировать этих актеров в Simple Injector. Вы должны постоянно сохранять свою конфигурацию (https://simpleinjector.readthedocs.io/en/latest/howto.html#verify-configuration). – Steven

+0

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

Смежные вопросы