Search code examples
c#.netsimple-injector

ContextDependent instance as Singleton in Simple Injector


I have a question regarding ContextDependentExtensions of SimpleInjector library. We have the following extension to add possibility adding elements into injector with some context: ContextDependentExtensions

Example:

var container = new Container();
container.RegisterWithContext(logger => new SimpleLogger("Logger Constructor Parameter"));

Here is one interest line: Should always be transient!

So, what does it mean? And can we use here Lifestyle.Singleton or Lifestyle.Scoped lifestyle?

Can someone explain it for me? Thanks in advance.


Solution

  • With the RegisterWithContext extension method, you register a predicate that allows you to build up an instance using the information about the consuming component. This information is supplied to the predicate by the extension method.

    Since this means that you can built up a completely different instance per consuming type, making the registration anything else but transient, could lead to very weird behavior. Imagine for instance the registration of an ILogger abstraction where you create a Logger<T> implementation where the T is the type of the consuming component:

    container.RegisterWithContext(c =>
        (ILogger).container.GetInstance(
            typeof(Logger<>).MakeGenericType(c.ImplementationType)));
    

    If you would make this registration singleton, this would cause problems, even if the consuming components are singleton, because the same instance would be injected into each consumer; while each consumer would require their own instance, because they require their own closed-generic version, namely: Logger<Consumer1>, Logger<Consumer2>, Logger<Consumer3>, etc. Instead, very consumer would get the same instance; the instance that was created for the first resolved consumer. This is obviously awful.

    This same problem will exist as well when using Scoped instances; you'll get the same instance for the duration of the scope, which is typically not what you want; the instance should be context dependent.

    This is a serious limitation of the RegisterWithContext extension method that was supplied with the Simple Injector v2 documentation. Since this was too limiting, Simple Injector v3 now contains a built-in RegisterConditional method that replaces the RegisterWithContext extension method. The v3 docs never refer to RegisterWithContext anymore and we would advice to use RegisterWithContext instead.

    The documentation describes how to use the RegisterConditional and shows the following example:

    container.RegisterConditional(
        typeof(ILogger),
        c => typeof(Logger<>).MakeGenericType(c.Consumer.ImplementationType),
        Lifestyle.Singleton,
        c => true);
    

    Since the predicate of this registration returns true, the registration isn't really conditional, but simply contextual.

    Using this code you can return a Logger<T> specific to the consuming component, but still ensure that there is at most one instance of each closed Logger<T> type; thus a singleton.

    The main difference between the new RegisterConditional and the old RegisterWithContext is that you can't supply a factory delegate to create instances. With RegisterConditional, Simple Injector is in control over the creation of instances (instead your delegate) and this allows the creation of types to be completely incorporated in the pipeline and allows Simple Injector to verify and diagnose the registered component and its dependencies.