Understanding Disposables In .NET Dependency Injection – Part 2

Following on from Part 1 where I provide an overview of hiding the Dispose method from consumers of the Dependency Injection container, in this part, I move on to dealing with objects that are created outside, but registered with the DI container.

Background

In part 1, I included the table below of extension methods that can be used to register services and implementations with the Microsoft DI container which indicates which of these will automatically dispose of objects that implement the IDisposable interface.

Method Automatic Disposal
Add{LIFETIME}<{IMPLEMENTATION}>() Yes
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>() Yes
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION}) Yes
AddSingleton<{SERVICE}>(new {IMPLEMENTATION}) No
AddSingleton(new {IMPLEMENTATION}) No

In the last two of these methods, the instance is explicitly instantiated using the new keyword. Note, this style of registration is only available for Singletons and is not supported for Scoped or Transient lifetimes.

Wherever possible, if the class being registered implements IDisposable, I would encourage you not to use the last two methods, and instead use the third method where the instantiation takes place inside a lambda expression. This simple change of signature ensures that the object will be disposed by the container when its lifetime comes to an end.

As described in Part 1, I would also avoid registering the class as the service type as this will allow the consumer to call the Dispose method which can have unintended consequences, especially for singleton and scoped lifetime objects where the object may still be required by other consumers.

If for some reason it is not possible to use one of the other methods and a new instance must be instantiated outside of the container, there are ways of ensuring that the object is disposed of by the container.

Disposing Instantiated Singletons

If instances are instantiated as part of the container registration process (for example within the StartUp class’s ConfigureServices method), there is no natural place to dispose of these objects and therefore, they will live for the during of the application.

In most cases, this is not a major problem as, if written correctly, they will be disposed of when the application ends. However, we should try to explicitly clean up after ourselves when using unmanaged resources, but in this case, how?

When using ASP.NET Core, the Host takes care of registering a number of services for you that you may not be aware of.

One of the services that gets registered is IHostApplicationLifetime. This has three properties which return cancellation tokens which can be used to register callback functions that will be triggered when the host application starts, is about to stop and finally stops.

With this in place, we can register a callback to our StartUp class to dispose of our objects when the application is about to stop.

In order to do this, we can approach this in one of two ways, depending on where and how the object has been created during registration.

Startup Class Scoped Variable

If the instance has been created in the constructor of the StartUp class and assigned to a class level variable, this variable can be used to dispose of the object within the callback registered with the ApplicationStopping token returned from the IHostApplicationLifetime.

If you have several disposable singleton instances created in this manner, they can all be disposed of within the one method.

Instance Created Inside the ConfigureServices Method

If the instance has been created ‘on-the-fly’ within the registration and not captured in a class variable, we will need to obtain that instance from the container in order to dispose of it.

In order to obtain the instance, we need to request that instance within the Configure method in the StartUp class which gets called after the container has been created.

If you have several disposable singleton instances created in this manner, they can all be disposed of within the one method. However, obtaining these instances becomes a bit messy as you need to request them all either in the Configure methods parameter signature (which can become quite lengthy if more than a couple of types are required) or use the IApplicationBuilder’s ApplicationServices property to get access to the container’s IServiceProvider instance and use the GetService method to obtains the container registered instances.

Next Time

In Part 3 of this series, I will discuss hiding the Dispose method using intermediate classes based on common design patterns.