Search code examples
simple-injector

Do I need to pass distinct instances of WebApiRequestLifestyle to register different classes?


Do I need to pass distinct instances of WebApiRequestLifestyle to register different classes?

Or can I pass the same object in every call to container.Register()?

Or, rather, is there a difference between:

container.Register(() => new MyClass1(), new WebApiRequestLifestyle());
container.Register(() => new MyClass2(), new WebApiRequestLifestyle());

and:

var webApiRequestLifestyle = new WebApiRequestLifestyle();
container.Register(() => new MyClass1(), webApiRequestLifestyle);
container.Register(() => new MyClass2(), webApiRequestLifestyle);

Solution

  • No, you can reuse the same instance; It's designed to be reused. Under the covers Lifestyle instances (such as Lifestyle.Singleton,Lifestyle.Transient, and WebRequestLifestyle) are actually reused all the time.

    Users are especially expected to reuse the same instance when dealing with scoped lifestyles, such as WebRequestLifestyle, WebApiRequestLifestyle, and LifetimeScopeLifestyle. They all inherit from the ScopedLifestyle base class and the idea is that you might have multiple end applications (such as a web application, console application, and WCF service) that share the same business logic. In that case you want to extract the shared part of the registration to allow it to be reused by all applications. In that case however, each end application will need its own special scoped lifestyle, but that shared registration code shouldn't depend on a special lifestyle. That shared registration logic might look like this:

    public static void BootstrapBusinessLayer(Container container, 
        ScopedLifestyle lifestyle)
    {
        container.Register<IUnitOfWork, MyDbContext>(lifestyle);
        container.RegisterOpenGeneric(typeof(IRepository<>), 
            typeof(SqlRepository<>), 
            lifestyle);
    
        container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>), lifestyle,
            AppDomain.CurrentDomain.GetAssemblies());
    
        // etc
    }
    

    Here you see that the lifestyle instance is reused in multiple registrations, and this was the main scenario we had in mind when we defined the ScopedLifestyle instance. In this case it would be very inconvenient if that shared code should create a new lifestyle, since the method should be forced to accept an Func<ScopedLifestyle> instead of simply accepting a ScopedLifestyle.

    Lifestyle implementations are thread-safe and don't contain any information about the registration (or the Container instance). The framework calls their Lifestyle.CreateRegistration method to build a new registration with that particular lifestyle.

    The only reason to create multiple instance (for the built-in ones at least) is when you have some type that needs to be disposed while a different one should not be disposed when the scoped lifestyle ends. In the latter case you explicitly need to create a lifestyle class with disposing disabled. This however is a quite uncommon scenario.

    Another reason to reuse the same instance is that this optimizes performance. This is actually quite unintuitive, but Simple Injector is able to optimize the performance of resolving object graphs in cases where multiple services are resolved within the same (compiled) delegate, if they are originate from the same Lifestyle instance.

    Under the covers, Simple Injector builds expression trees and in the end those expression trees are compiled down to delegates. What you will see is that often one single delegate is compiled for a whole part of the object graph, especially for the part of the graph that is transient, with non-transient leafs. So take the following registration for instance:

    var webRequestLifestyle = new WebRequestLifestyle();
    
    container.Register<ShippingController>();
    container.Register<ICommandHandler<ShipOrder>, ShipOrderHandler>();
    container.Register<ILogger, SqlLogger>(webRequestLifestyle);
    container.RegisterPerWebRequest<IUnitOfWork, DbContext>(webRequestLifestyle);
    

    Now consider the following object graph that will be built:

    ILogger logger = new SqlLogger();
    IUnitOfWork uow = new DbContext();
    
    new ShippController(
        logger,
        uow,
        new ShipOrderController(
            logger,
            uow));
    

    Here you see that both the ILogger and IUnitOfWork are injected multiple times in the same graph, but since they are registered with a scoped lifestyle, the same instance is reused throughout the graph.

    To ensure that the same instances are reused, Simple Injector caches those instances in a SimpleInjector.Scope instance. So in fact the expression tree will actually more look like this:

    new ShippController(
        loggerProducer.GetInstance(),
        uowProducer.GetInstance(),
        new ShipOrderController(
            loggerProducer.GetInstance(),
            uowProducer.GetInstance()));
    

    Here the loggingProducer and uowProducef variables are InstanceProducer instances and they call back into the internal Scope instance to make sure on the second call the instance is retrieved from the cache (the scope). Downside of this is that a call to GetInstance should again determine what the current scope is (which might include some querying thread-local storage and a dictionary lookup, which is the case with the WebRequestLifestyle). If the object graph is big, this performance penalty can add up.

    So instead, before compiling this expression to a delegate, Simple Injector optimizes this by walking the expression tree and it looks for producers that use the same ScopedLifestyle instance. If producers are using the same ScopedLifestyle instance, we know for sure that they will use the same Scope instance, and in that case it would be useless to request that same Scope multiple times. So Simple Injector optimizes the expression to something like this:

    var scope = webRequestLifestyle.GetCurrentScope();
    var logger = scope.GetInstance<ILogger>();
    var uow = scope.GetInstance<IUnitOfWork>();
    
    new ShippController(
        logger,
        uow,
        new ShipOrderController(
            logger,
            uow));
    

    Note that this is a bit simplified. In reality the graph will be a bit more complex, since Simple Injector ensures that instances are created lazily, but that isn't really an issue with this graph. Important thing however is, that in this case now the Scope instance is just requested once, while in the unoptimized version, the Scope was requested 4 times, and for each service, now only one dictionary lookup is needed, instead of the two lookups that were needed before.

    The thing is however, this optimization can only take place in case all registrations use the same Lifestyle instance, because that's the only way we can know for sure that those instances use the same Scope. If you create a new Lifestyle instance for each registration, the object graph isn't optimized as much as it would with the same instance.

    Do note however, that Simple Injector optimizes very aggressively. Most of the time the advantages of these optimizations are lost in all the other things your application does (such as querying the database). So in the common case you wouldn't even notice the loss of performance and in that case Simple Injector would still be about 100 to a 1000 times as fast as other DI containers.