It seems that Container.Collection.Register
does not have an overload which takes a Lifestyle
. All discovered implementations will be registered with the default Lifestyle
. What is the reasoning behind the omission of such an overload?
What is the preferred way of adding a collection where all items should have a Lifestyle
that is not the default lifestyle?
What is the reasoning behind the omission of such an overload?
First of all, because, (as Eric Lippert stated):
no one ever designed, specified, implemented, tested, documented and shipped that feature. All six of those things are necessary to make a feature happen. All of them cost
Second, the Collection.Register
overloads that accept a list of Type
instances (e.g. Collection.Register<TService>(params Type[])
), accept a list of both implementations and abstractions. Supplying a Lifestyle
for abstractions wouldn't make much sense and is even confusing.
To understand why Simple Injector allows supplying abstractions, take a look at the following registration:
container.Collection.Register<ILogger>(
typeof(ILogger),
typeof(ISpecialLogger),
typeof(SqlLogger));
container.Register<ILogger, DefaultLogger>(Lifestyle.Scoped);
container.Register<ISpecialLogger, SpecialLogger>(Lifestyle.Singleton);
In this example, a collection of loggers is registered, where two of the supplied types are abstractions. The idea behind allowing supplying abstractions is that, with it, you can point them to other registrations. This is exactly what the previous example does. When resolving the collection of loggers, it will consist of the Scoped
DefaultLogger
, the Singleton
SpecialLogger
, and the Transient
SqlLogger
.
Now consider a hypothetical new Collection.Register
overload that accepts a Lifestyle
:
container.Collection.Register<ILogger>(new[]
{
typeof(ILogger),
typeof(ISpecialLogger),
typeof(SqlLogger)
},
Lifestyle.Transient);
container.Register<ILogger, DefaultLogger>(Lifestyle.Scoped);
container.Register<ISpecialLogger, SpecialLogger>(Lifestyle.Singleton);
What does it mean to have the all elements being Transient
, while two of the elements point to registrations of different lifestyles?
We haven't found a good API that solves these issues (yet), which is why the container is lacking such overload.
What is the preferred way of adding a collection where all items should have a Lifestyle that is not the default lifestyle?
There are multiple ways to do this.
Collections, registered in Simple Injector, use a fallback mechanism to determine their lifestyle. This is done by checking whether there exists a concrete registration for the collection's element. For instance:
container.Collection.Register<ILogger>(typeof(SqlLogger), typeof(FileLogger));
// Ensures that SqlLogger is a Singleton when part of the IEnumerable<ILogger>.
container.Register<SqlLogger>(Lifestyle.Singleton);
When the IEnumerable<ILogger>
is first resolved by Simple Injector, for each element it will (in the following order):
Register<SqlLogger>
Container.Options.LifestyleSelectionBehavior
(which defaults to Transient
).Instead of supplying a list of types or assemblies to Collection.Register
, you can also supply a list of Registration
instances. A Registration
describes the creation of a certain component for a certain lifestyle and this class is used by Simple Injection internally when you call Container.Register
or Container.Collection.Register
. But you can also create Registration
instances manually and supply them to a Collection.Register
overload as follows:
// Load the list of types without registering them
Type[] types = container.GetTypesToRegister<ILogger>(assemblies);
// Register them using the overload that takes in a list of Registrations
container.Collection.Register<ILogger>(
from type in types
select Lifestyle.Transient.CreateRegistration(type, container));
This forces all registered types of the collection to have the Transient
lifestyle. You can also give each type its own specific lifestyle if you want.
When no registration can be found, Collection.Register
makes the registration itself, last minute, using the configured LifestyleSelectionBehavior
. The default selection behavior always returns Lifestyle.Transient
, but this behavior can be changed. For instance:
class CustomLifestyleSelectionBehavior : ILifestyleSelectionBehavior
{
public Lifestyle SelectLifestyle(Type implementationType) =>
implementationType == typeof(SqlLogger)
? Lifestyle.Singleton
: Lifestyle.Transient;
}
This implementation if, obviously, a bit naive, but shows the concept. You can override the default behavior as follows:
container.Options.LifestyleSelectionBehavior =
new CustomLifestyleSelectionBehavior();
Next to Collection.Register
, which allows the registration of all elements in one go, you can make use of the Collection.Append
methods. They allow one-by-one registration of collection elements:
container.Collection.Append<ILogger, SqlLogger>(Lifestyle.Singleton);
container.Collection.Append<ILogger, FileLogger>(Lifestyle.Transient);
There are also non-generic overloads available, which simplify auto-registering these types, for instance when returned using Container.GetTypesToRegister
.
These are your basic options in Simple Injector v4.6. We might decide to add a convenient Collection.Register<T>(IEnumerable<Type>, Lifestyle)
overload in the future, as the ommision of this overload does cause some confusion from time to time.