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:
- single
Foo
implementation registered to bothIFoo<Foo>
and toIFoo
(partially works) - single stream of
IFoo
which isIEnumerable<IFoo>
(it works) - Iterating of
IEnumerable
above should give me sameFoo
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: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 forIFoo<Foo>
andIFoo
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 isTransient
.This is why although you already registered
Foo
twice (once asIFoo<Foo>
and once asIFoo
), 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 callContainer.Verify()
.To solve this, you can change your configuration to the following: