I have a following class:
public class UserService : IUserService, IObserver<User>
I have registered IUserService
as a Singleton to UserService
. After that I do register the IObserver
this way:
container.RegisterManyForOpenGeneric(typeof(IObserver<>),
container.RegisterAll,
typeof(IObserver<>).Assembly);
So now if I inject IEnumerable<IObservable>
to my composite pattern and when I will enumerate will I get the same object as when resolving IUserService
?
It seems that it works this way (only one object of UserService
exists) at least if I resolve the IEnumerable
first. I was not able to find that in the documentation, but is this the feature that Simple Injector supports or it's a coincidence that can be changed at any time? And if yes, then I guess this behavior is the same for any two or more interfaces, not necessarily open generic?
The behavior you see is by design and described in the documentation for the container.RegisterCollection(Type, IEnumerable) method (RegisterAll
in v2.x):
Registers a collection of serviceTypes, whose instances will be resolved lazily each time the resolved collection of serviceType is enumerated. The underlying collection is a stream that will return individual instances based on their specific registered lifestyle, for each call to
IEnumerator<T>.Current
. The order in which the types appear in the collection is the exact same order that the items were registered, i.e the resolved collection is deterministic.
Although the API documentation states this, I must admit that the documentation isn't very clear about this. There are other places where this behavior is described, but this information is a bit buried deep down in the docs.
But basically, the API is designed to allow this:
container.Register<ILogger, DefaultLogger>(Lifestyle.Transient);
container.Register<SqlLogger>(Lifestyle.Scoped);
container.Register<FileLogger>(Lifestyle.Singleton);
container.RegisteCollection<ILogger>(new[] {
typeof(MailLogger),
typeof(SqlLogger),
typeof(FileLogger),
typeof(ILogger)
});
This configuration has a collection of ILogger
implementations, where the first element in the collection (MailLogger
) will be a transient (in case the LifestyleSelectionBehavior
isn't overridden), the second (SqlLogger
) will be Per Request, the third (FileLogger
) will be a singleton, and the fourth element will resolve a DefaultLogger
as transient.
it's a coincidence that can be changed at any time?
No, this is documented, tested, and supported. Although we will (by definition) introduce breaking changes in the next major release, it's very unlikely that we would change this behavior.
UPDATE: Note that this behavior hasn't changed in v3.
when you have two interfaces on the same class: will it will be the same class if I register the second interface on the same class?
No, it will not. This will in fact lead to a Torn Registration, which is something the Diagnostic Services warn about. Basically, if you do this:
container.Register<IFoo, FooBar>(Lifestyle.Singleton);
container.Register<IBar, FooBar>(Lifestyle.Singleton);
In this case the container will resolve two separate instances for FooBar
. That's because Simple Injector focusses on the registration (the abstraction), not the implementation (compared to Unity for instance, that works the other way around). In case you need exactly 1 FooBar
, you will have to do the following:
var registration = Lifestyle.Singleton.CreateRegistration<FooBar>(container);
container.AddRegistration(typeof(IFoo), registration);
container.AddRegistration(typeof(IBar), registration);
In your case that means that you will have to do this:
var registration = Lifestyle.Singleton.CreateRegistration<UserService>(container);
container.AddRegistration(typeof(IUserService), registration);
container.AddRegistration(typeof(UserService), registration);
container.RegisterCollection(typeof(IObserver<>),
new[] { typeof(IObserver<>).Assembly });
// Simple Injector v2.x syntax
container.RegisterManyForOpenGeneric(typeof(IObserver<>),
container.RegisterAll,
typeof(IObserver<>).Assembly);