Search code examples
inversion-of-controlsimple-injector

Simple Injector gives lifestyle mismatch error while both classes are registered as singleton


So I have this Simple Injector IoC wire up code

container.RegisterSingleton<IMsgProcessor, PrometheusTickerPublisher>();
container.RegisterSingleton<ICollector, UpdatesPerSecondDataCollector>();

And when I try and run my app I am getting this error message

A lifestyle mismatch is encountered. PrometheusTickerPublisher (Singleton) depends on UpdatesPerSecondDataCollector (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.

I don't get this at all. Both items are registered as singletons. I then changed the registration to this, to see if I could be more explicit about it being a singleton

container.RegisterSingleton<IMsgProcessor, PrometheusTickerPublisher>();
container.AddRegistration(typeof(ICollector), CreateCollectorRegistration(container));

internal static Registration CreateCollectorRegistration(Container container)
{
    return
        Lifestyle.Singleton.CreateRegistration<ICollector>(
            () =>
            {
                var timeProvider = container.GetInstance<ITimeProvider>();
                return new UpdatesPerSecondDataCollector(timeProvider);
            }, container);
}

But this gives the same error

At this point I am completely lost, Any ideas


Solution

  • Calling Container.Verify() at the end of the registration process will check the container's health. If you do that, it would give you even more feedback:

    The configuration is invalid. The following diagnostic warnings were reported:

    -[Lifestyle Mismatch] PrometheusTickerPublisher (Singleton) depends on UpdatesPerSecondDataCollector (Transient).

    -[Short Circuited Dependency] PrometheusTickerPublisher might incorrectly depend on unregistered type UpdatesPerSecondDataCollector (Transient) instead of ICollector (Singleton).

    -[Ambiguous Lifestyles] The registration for UpdatesPerSecondDataCollector (Transient) maps to the same implementation (UpdatesPerSecondDataCollector) as the registration for ICollector (Singleton) does, but the registration maps to a different lifestyle. This will cause each registration to resolve to a different instance.

    -[Ambiguous Lifestyles] The registration for ICollector (Singleton) maps to the same implementation (UpdatesPerSecondDataCollector) as the registration for UpdatesPerSecondDataCollector (Transient) does, but the registration maps to a different lifestyle. This will cause each registration to resolve to a different instance. See the Error property for detailed information about the warnings.

    Please see https://simpleinjector.org/diagnostics how to fix problems and how to suppress individual warnings.

    The most interesting part here is the Short Circuited Dependency warning:

    -[Short Circuited Dependency] PrometheusTickerPublisher might incorrectly depend on unregistered type UpdatesPerSecondDataCollector (Transient) instead of ICollector (Singleton).

    In other words, Simple Injector detects that you registered UpdatesPerSecondDataCollector through its ICollector as Singleton, but you are circumventing that ICollector registration by depending directly on the UpdatesPerSecondDataCollector implementation instead. This is called short-circuited, because instead of using the ICollector registration, you go directly to the (unregistered) implementation. This might cause all sorts of problems.

    Because there is no explicit registration for UpdatesPerSecondDataCollector, Simple Injector will implicitly register it for you, and it uses the Transient lifestyle (unless you specify otherwise). The registration for ICollector, however, is a Singleton, so that will likely cause problems. This is again noted in the Ambiguous Lifestyles warning:

    -[Ambiguous Lifestyles] The registration for UpdatesPerSecondDataCollector (Transient) maps to the same implementation (UpdatesPerSecondDataCollector) as the registration for ICollector (Singleton) does, but the registration maps to a different lifestyle. This will cause each registration to resolve to a different instance.

    Without calling Verify, all these issues stay undetected. The only verification Simple Injector does when resolving a type is the Lifestyle Mismatch, which is why you only got the following error:

    A lifestyle mismatch is encountered. PrometheusTickerPublisher (Singleton) depends on UpdatesPerSecondDataCollector (Transient).

    Long story short, you didn't register UpdatesPerSecondDataCollector by itself, but only by its ICollector abstraction, causing Simple injector to implicitly make a Transient registration for UpdatesPerSecondDataCollector on your behalf. Because this registration is Transient, Simple Injector blocked the injection of it into the Singleton class PrometheusTickerPublisher. If you would have called Container.Verify(), there would be a very detailed problem description of what is going wrong, which is much more than just a simple Lifestyle Mismatch.