Search code examples
c#simple-injector

SimpleInjector.ActivationException due to collection lifestyle


I am trying to switch my DI container from Unity to SimpleInjector and I am struggling a bit with collections.

I have two concrete implementations of IExceptionLogWriter that I want to register as singletons so I do the following:

container.RegisterSingleton<RaygunExceptionLogWriter>();
container.RegisterSingleton<SqlExceptionLogWriter>();

Now I want to register both of those with a collection so I do this:

container
.RegisterCollection<IExceptionLogWriter>(
    new[] { 
        typeof(RaygunExceptionLogWriter), 
        typeof(SqlExceptionLogWriter) });

Now when I try run the application I get the following error:

Additional information: A lifestyle mismatch is encountered. ExceptionLogger (Singleton) depends on IExceptionLogWriter[] (Transient). Lifestyle mismatches can cause concurrency bugs in your application. Please see https://simpleinjector.org/dialm to understand this problem and how to solve it.

If I understand the error correctly it looks like I need to somehow register IExceptionLogWriter[] as a singleton since I registered RaygunExceptionLogWriter and SqlExceptionLogWriter as singletons. Maybe I am being dense but I cannot for the life of me figure out how to do that.

I tried this:

container.RegisterSingleton<IExceptionLogWriter[]>();

That obviously crashed and burned.

Edit - All Pertinent Registrations

container.RegisterSingleton<SqlConnectionFactory>(new SqlConnectionFactory(connectionString));
container.RegisterSingleton<IApplicationExceptionRepository, SqlApplicationExceptionRepository>();
container.RegisterSingleton<IErrorHandler, ErrorHandler>();
container.RegisterSingleton<IExceptionLogger, ExceptionLogger>();
container.RegisterSingleton<RaygunExceptionLogWriter>();
container.RegisterSingleton<SqlExceptionLogWriter>();
container
.RegisterCollection<IExceptionLogWriter>(
    new[] { 
        typeof(RaygunExceptionLogWriter), 
        typeof(SqlExceptionLogWriter) });

And the related constructors:

public SqlConnectionFactory(string connectionString)
public SqlApplicationExceptionRepository(SqlConnectionFactory connectionFactory)
public ErrorHandler(IExceptionLogger exceptionLogger)
public ExceptionLogger(IExceptionLogWriter[] exceptionLogWriters)
public RaygunExceptionLogWriter()
public SqlExceptionLogWriter(IApplicationExceptionRepository applicationExceptionRepository)

Solution

  • What is happening here is that your ExceptionLogger component has a constructor argument of type IExceptionLogWriter[] (an array of IExceptionLogWriter). Simple Injector automatically resolves array for you, but the implicit registration that Simple Injector makes is transient. The reason for this is that:

    • An array is list of references. In case the list contains a component that is transient, injecting the array as singleton will cause Captive Dependencies.
    • An array is a mutable type; everybody can change the contents of the array. Making it singleton might cause unrelated parts of your applications to fail because of changes to the array.

    So for safety reasons, Simple Injector makes the array registration transient. This allows the diagnostic system to kick in and warn you about any captive dependency it might cause.

    The solution is simple and you have two options:

    1. Make the ExceptionLogger consumer transient, or
    2. Change ExceptionLogger's dependency from IExceptionLogger[] to either IEnumerable<IExceptionLogger>, IList<IExceptionLogger>, ICollection<IExceptionLogger>, IReadOnlyList<IExceptionLogger> or IReadOnlyCollection<IExceptionLogger>.

    For these collection abstractions Simple Injector will inject a collection that:

    • Can't be changed from the outside, and
    • Prevents captive dependencies by resolving an element according to its lifestyle every time the collection is iterated.