In SimpleInjector documentation it is said:
Simple Injector preserves the lifestyle of instances that are returned from an injected
IEnumerable<T>
,ICollection<T>
,IList<T>
,IReadOnlyCollection<T>
andIReadOnlyList<T>
instance. In reality you should not see the the injectedIEnumerable<T>
as a collection of instances; you should consider it a stream of instances. Simple Injector will always inject a reference to the same stream (theIEnumerable<T>
orICollection<T>
itself is a singleton) and each time you iterate theIEnumerable<T>
, for each individual component, the container is asked to resolve the instance based on the lifestyle of that component.
After reading this I expected that after I've registered all IFoo
and IFoo<T>
as singletons, IEnumerable<IFoo>
will be singleton itself and it enumeration will always lead to the same objects.
But this piece of code doesn't work like that:
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();
}
}
What I'm trying to achieve:
Foo
implementation registered to both IFoo<Foo>
and to
IFoo
(partially works)IFoo
which is IEnumerable<IFoo>
(it works)IEnumerable
above should give me same Foo
implementation (doesn't work)Is it possible?
What's happening here is the following:
The call to cont.RegisterCollection<IFoo>(new[] {Assembly.GetExecutingAssembly()})
results in the following registration:
cont.RegisterCollection<IFoo>(new[] { typeof(Foo) });
When the collection is resolved for the first time, the container will look up the registration for Foo
to make sure it will reuse that registration and thus its lifestyle.
With your registration however, no registration for Foo
can be founds. There are registrations however for IFoo<Foo>
and IFoo
but what Simple Injector is concerned, those are different registrations.
As a result of this, Simple Injector will create a last-minute registration for Foo
on your behalf and the default lifestyle that is used by Simple Injector is Transient
.
This is why although you already registered Foo
twice (once as IFoo<Foo>
and once as IFoo
), the element of the collection will still be transient.
You would have noticed this problem if you had called Container.Verify()
at the end of the registration process. Simple Injector detects these kinds of misconfigurations for you. You should always call Container.Verify()
.
To solve this, you can change your configuration to the following:
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();
...
}