Understanding Disposables In .NET Dependency Injection – Part 3

Following on from Parts 1 and 2, in this final part of the series, I move on to dealing with types that you do not have source control for and therefore cannot change directly to hide the Dispose method using the techniques I have described in the previous posts.

Background

In the previous two parts of this series, I have made the assumption that you are able to amend the source code for types that implement IDisposable.

But what happens, if you don’t have the source code? This is where some well known design patterns come in useful.

Design Patterns

When trying to hide disposability from container consumers, the general principle (as shown in the previous parts of this series), is to use an interface that excludes the dispose method from its definition so that the consumer only receives the instance via the interface and not as the concrete class, thus hiding the Dispose method.

If we do not have the source code, we need to create an intermediary between the interface that we want to expose and the type that we want to use.

There are four classic design patterns , each a variation of the intermediary theme, that we can use together to achieve our goal.

I will not go into an in depth description of the patterns here as there are plenty of resources that can be a much better job, however, here is a brief overview.


Adapter and Bridge Patterns

You may already have another interface, either your own or some other third party that achieves the goal, but for which you do not have the source code or cannot change as it would break other dependencies. In this case, the adapter pattern is used to fill the gaps to make the two interfaces work with each other.

If the desired interface does not already exist, this is becomes the bridge pattern which does the same thing, but the interface design is within your control (whereas adapter uses an interface outside your control)

Façade Pattern

The core aim of our intermediary is to remove the Dispose method and in effect simplify the interface. If the third party has a number of members that you are not interested in or that need to be brought together into a single method, the Façade pattern can be used.

Proxy Pattern

The proxy pattern is used to prevent direct access to an object. It usually has an identical interface to the class that it represents.

Decorator Pattern

The decorator pattern is similar to the proxy pattern, but it can may additional functionality to enhance behaviour.


For our purposes, you are likely to use an adapter pattern if you do not have control over either interface, but more likely, you will be designing the interface to be used by consumers and therefore will write a custom class that brings together elements of the other three patterns, namely

  • Bridge – we will be creating one between the desired interface to receive calls and the target interface
  • Façade – we will be simplifying the interface to remove any members not needed
  • Proxy – passing through calls to members of our class on to the instance that we are hiding
  • Decorator – we may be taking the opportunity to do some additional work such as logging calls

Putting It Together

In the example below we have a class SomeDisposable that we do not have the source code for. The class implements IDisposable and has multiple methods, but only one of interest, namely – DoSomething().

Rather than register the class directly, we want to wrap it with an intermediary that will

  • Create an instance of DoSomething inside the constructor (as we do not want to register the DoSomething class with the container)
  • Implement a simplified interface (façade) of just the one DoSomething() method
  • Have a Dispose method (that is not exposed via the interface) that the container will call to proxy to the inner object’s Dispose() method
  • Decorate the inner DoSomething method with a call to the logger to log that the method has been called and when it has completed

Once we have the intermediary class, we can register this with the container in the StartUp class with the façade interface as the service type.

With this in place, we have managed to avoid consumers being able to dispose of the DoSomething singleton instance directly as it is hidden inside the intermediary class but the intermediary class (and in turn the inner instance) is still disposable by the container, but having put a façade in place, the consumer cannot call Dispose() directly.

Conclusion

That is the end of this series on preventing consumers causing problems by disposing of objects that may have a longer lifetime than the consumer when under the control of the .NET DI container.

I hope it has been of use.

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.

Understanding Disposables In .NET Dependency Injection – Part 1

In this post I will be discussing the traps that can catch you out by potentially creating memory leaks when registering types that implement the IDisposable interface as services with the out-of-the-box .NET Dependency Injection container.

Background

Typically, a type will implement the IDisposable interface when it holds unmanaged resources that need to be released or to free up memory.

More information about cleaning up resource can be found on Microsoft Docs

To keep things simple for the rest of this post, I will be referring to instances of types that implement IDisposable as “Disposable Objects”.

Managing Disposable Objects without Dependency Injection

Outside of dependency injection, if you create an instance of such a type, it is your responsibly to call the Dispose method on the class to initiate the release of unmanaged resources.

This can be done either

The two approaches are documented on the Microsoft Docs site.

Disposable Objects Created by the Dependency Injection Container

As a general rule, if the Dependency Injection container creates an instance of the disposable object, it will clean up when the instance lifetime (transient, scoped or singleton) expires (E.g. for scoped instances in ASP.NET Core, this will be at the end of the request/response lifetime but for singletons, it is when the container itself is disposed).

The following table (based on the table in the Microsoft Docs page) shows which registration methods will trigger the container to automatically dispose of the object.

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

As you can see from the table above, the three most common methods for adding services, where the container itself is responsible for creating the instance, will automatically dispose of the object at the appropriate time.

However, the last two methods do not dispose of the object. Why? It’s because in these methods, the objects have been directly instantiated with a new keyword and therefore, the container has not been responsible for creating the object.

Whilst they look similar to the third method, the difference is that the instance in that method has been created within the context of a lambda expression which is within the control of the container and therefore in the container’s control.

In the last two methods, the object could be created at the time of registration (by using the new statement) but then again, it may have been created outside these methods (either within the scope of the ConfigureServices method in the StartUp class, or at a class level) and therefore, the container cannot possibly know of where the object has been created, the scope of its reference,  and where else it may be used. Without this understanding, it cannot safely dispose of the object as this may throw an ObjectDisposedException if referenced elsewhere in code after the container has disposed of it.

I will come on to dealing with ensuring these objects referenced in these last two methods can be disposed of correctly in Part 2.

Hiding Disposability from Container Consumers

The first method in the table above is the simplest way to register a type. Consumers will request an instance of the object and make use of it.

However, if the type implements IDisposable, this means that the Dispose method is available to the consumer to call. This has repercussions depending on the lifetime that the dependency has been registered as.

For transients that have been created specifically to be injected into the consuming class, it is not the end of the world. If dispose is called on a transient, the only place that will suffer is the consuming class (and anything it passes the reference to) as any subsequent references to the object (or to be more specific, members in the type that check the disposed status) are likely to result in an ObjectDisposedException (this will depend on the implementation of the injected class).

For scoped and singleton lifetimes, things become more complicated as the object has a lifetime beyond the consumer class. If the consuming class calls Dispose and another consumer then also makes use of a member on the disposed class, that other consumer is likely to receive an ObjectDisposedException.

Therefore, we want to ensure that the Dispose method on the registered class is somehow hidden from the consumer.

There are several ways of hiding the Dispose method which are considered below

Explicit Implementation of IDisposable

The quick (and dirty) way of hiding the Dispose method that exists on a class is to change the Dispose method’s declaration from a public method to an explicit interface declaration (as shown below) so that it can only be called by casting the object to IDisposable.

It should, however, be recognised that this is just obfuscating the availability of the Dispose method. It does not truly hide it as the consumer may be aware that the type implements IDisposable and explicitly cast the object and call Dispose.

This is where extracting out other interfaces comes to our rescue when it comes to dependency injection.

Register the Implementation Type With a More Restrictive Interface

If we define an interface that has all the public members of our class except for the Dispose method and only make the object available by registering it in the DI container with the limited interface as the service, this will make it harder (but not completely impossible) for the consumer of the object to dispose of the object as the concrete type is only known to the container registration (unless the consumer uses GetType() of course, but that is splitting hairs and in many ways negates the whole point of using the container).

Of course, following the Interface Segregation Principle from SOLID, this interface may be broken down into smaller interfaces which the class registered against.

Next Time …

In Part 2 of this series on IDisposable in Dependency Injection, I will move on to dealing with those objects that the container will not dispose of for you.