Skip to content
Alice Waddicor By Alice Waddicor Software Engineering Apprentice III
Understanding dependency injection

Dependency Injection (DI) was one of the areas I had to get to grips with when I began my apprenticeship with endjin. Because DI deals with something as fundamental as the creation of new objects, it has been used by all of the applications I've encountered here.

This blog post sums up what I've learnt as a newcomer to DI, with the help of resources such as Dependency Injection in .NET by Mark Seemann.

The usual proviso for my blogs applies – it starts from the very basics. If you are familiar with DI and want something more in depth, there is a series of blogs by Mike describing endjin's composition framework. There is also an earlier series by Matthew (same link) discussing the principles and practicalities of creating a low-ceremony component discovery and composition framework using the Managed Extensibility Framework (MEF) and Castle Windsor. This was the predecessor to our current independent composition framework.

What's dependency injection?

A class has a dependency on any other class that it uses. When one object creates another object using the new keyword, this can result in tightly coupled code which is difficult to test, maintain and extend.

The Introduction to Rx.NET 2nd Edition (2024) Book, by Ian Griffiths & Lee Campbell, is now available to download for FREE.

Dependency injection is the principle that the objects which a class needs should be 'injected' into it, not created inside it. Injection usually happens through the constructor, where a class receives any objects it needs as constructor parameters.

noNews

Here's the important bit! This 'constructor injection' enables the automated discovery of dependency relationships – a technique used together with information about interface/ concrete type mappings to recursively create and inject the full set of objects needed by some top level object within an application.

There are a range of DI frameworks which you can plug in to your applications to access this functionality.

Why is dependency injection needed?

To give a more detailed description, we need to look in more depth at the problem that DI is trying to solve. What is so bad about classes creating the objects they need with the 'new' keyword?

It's common to declare member variables using an interface. The first problem is that where this member variable is initialised using the implementation type, the classes remain tightly coupled.

Programming C# 10 Book, by Ian Griffiths, published by O'Reilly Media, is now available to buy.

This may seem harmless in smaller applications, but it makes code difficult to maintain and extend. For example, if the concrete class's name or constructor signatures change, this change needs to be reflected in all of the classes which explicitly create an instance of this class, making code error prone and difficult to maintain (although this can be mitigated with tools such as ReSharper), particularly when a class has a dependency on a type in another package.

A further problem with traditional object creation is that testing becomes complicated. You can mock the behaviour of an interface in a test, before you've actually decided which concrete implementation to use. For example, if you have an IRepository<T> interface, you can mock it and test your class that has the dependency on IRepository<T> without needing the actual implementation. You can create a series of different scenarios for tests, simulating how the mock repository would behave in different situations. When the class creates a dependency using a concrete class, this brings the dependency wholesale into any tests you write, as test doubles (such as mocks) can't be created to represent their behaviour.

Traditional object creation using 'new' also gives classes responsibility for creating their dependencies. This is a violation of the single responsibility principle. It leads to the duplication of code throughout the application, where several classes are carrying out the same set of steps to create the same type.

Using a factory to create objects mitigates some but not all of these issues. A factory class can provide objects to its clients without them having to know the concrete types of the these objects. For example:

This removes the responsibility for object creation from the factory's client, and prevents duplication of the code necessary to create those objects.

However, the problem now shifts from tight coupling with the returned class to tight coupling with the factory itself. And, although the class requesting the object from the factory isn't responsible for creating the object, it is still still responsible for starting the process which creates this object. To some extent, this is still a violation of the single responsibility principle.

Proponents of dependency injection take the view that object creation and lifetime management should be viewed as a specialised function like managing data access or HTTP connections – it should be controlled by a specialised service which is separate from classes providing the application's core functionality.

Constructor injection prevents a class from having any responsibility for creating new objects, either directly, or indirectly through a factory. It also provides a discoverable convention for the way in which information about dependencies is presented throughout the application.

Is dependency injection the same as…?

When I'm learning something new, the thing I really need is to understand is how the main concepts I see in discussions about the subject relate to each other. Discussions of dependency injection often bring in lots of other terms, and it's not obvious to the newcomer:

  • what's another term for dependency injection
  • what's a necessary part of dependency injection
  • what's an optional part of dependency injection
  • what's a wider principle that dependency injection fits into.

Here're the results of my mental filing:

DI concepts

Loose coupling

The design principle that the elements of a system should have as little knowledge as possible of the other elements of the system. Where components are loosely coupled, if component A depends on component B, when component B changes, it shouldn't necessarily affect Component A (unless there is a breaking change to the public interface).

More specifically, some code quality metrics systems such as NDepend measure coupling for an individual class in terms of its 'afferent coupling' (the number of types that depend directly on it), and its efferent coupling (the number of types that it depends on directly).

DI is a a means of achieving loose coupling.

Programming to an interface

One of a set of practices used to achieve loose coupling. An interface defines the intention and contract of how you want an external dependency to behave, but leaves it up to its concrete implementations to actually implement this behaviour. For example if you have an IRepository<T> interface and it has a method called <T> Get(string id), it's making a contract that for a given id you will return an instance of T, but the concrete implementation of the repository, StoreRepositiory : IRepository<Store>, will actually provide the implementation.

DI enables classes to interact with other classes through their interfaces throughout an application.

Inversion of Control (IoC)

The design principle that responsibility for controlling application lifecycle and object creation should be handled by separate frameworks, rather than the classes providing the core logic of an application. For example, when the .NET framework controls the execution lifecycle of an application, it is providing inversion of control. DI is another example of inversion of control. The relationship between the two concepts is explained in Martin Fowler's blog Inversion of Control Containers and the Dependency Injection pattern. Because DI is the type of IoC that developers most directly engage with, the term IoC is often used to mean DI.

Constructor injection

The core technique used to achieve DI. Constructor injection is the practice of passing an object to another object through its constructor, rather than asking the consumer object to create its own instance.

This passes responsibility for managing dependencies up the chain of command. Other types of injection (property injection and interface injection) are possible, but constructor injection is the most clear and straightforward way of enabling dependency injection as it makes each type's dependencies explicit. Other types of injection are useful in particular circumstances. With property injection (also known as setter injection), a class has a property for its dependency, using an interface as its type. The dependency is injected into the class using the property's setter. This is useful in awkward places where you can't create a type via a DI framework container – for example a WPF control or UI element, which is generally instantiated by the design surface inside the IDE at design time or another aspect of the UI framework at runtime.

DI framework

A library which is used to create and manage dependencies. A DI framework may include a container class which exposes methods to create and retrieve objects. I'll be using this term throughout this blog to describe a library which is used to achieve DI. It is possible to carry out DI without using a DI framework, by writing your own specialised classes.

Inversion of control container

Used as a synonym for DI framework.

DI container

Sometimes used as a synonym for DI framework, sometimes used to refer specifically to the container class provided by many such frameworks.

Composition framework

A composition framework provides dependency injection as part of a wider programme of managing component discovery, management and composition. A component is defined by Martin Fowler as 'a glob of software that's intended to be used, without change, by an application that's out of the control of the writers of the component'. The term suggests something that can include several classes. However, managing composition at this level trickles down into managing the creation of individual types which make up these components, using dependency injection.

Most dependency injection frameworks could be described as composition frameworks as they deal with the discovery and composition of components, not just the injection of dependencies into components. I'll be using the terms interchangeably in this blog.

Castle Windsor, Ninject, Unity, Spring, AutoFac

Popular DI frameworks.

Service Locator

A factory-based design pattern under which a single entity is responsible for retrieving services throughout an application, without the client classes having to know the concrete types being returned. Viewpoints vary as to whether you can view some parts of a DI framework as a Service Locator, or whether a Service Locator is something completely distinct to DI. Mark Seemann's blog Service Locator is an anti-pattern takes the position that although parts of a DI framework may look like a Service Locator, the correct  use of DI means that the Service Locator pattern is not in place. Others view the Service Locator pattern as one part of a DI framework.

Repository

A design pattern used to abstract data access. A repository class is a typical example of a class that might be created and used in more than one place in an application. When DI is in place, a Repository will frequently be one of the objects handled by constructor injection – which is why you see it in discussions of DI.

How does a dependency injection framework work?

Dependency injection frameworks keeps track of which type belongs to which abstract class or interface, and create and return objects of that type on request.

They perform three fundamental roles:

  1. Registration – receive or just store information about which concrete types map to which abstractions

  2. Type discovery – extract information about each class's dependencies

  3. Resolution - return an object of the correct type, given an abstraction.

I'll look at these roles in turn.

Registration

The framework will usually include a container class, which stores information about which abstract types map to which concrete types.

A fundamental distinction among DI frameworks is those that use configuration files to map abstract types to concrete types, those that use code as configuration, and those that use convention-based automatic registration.

In the case of a configuration-based DI framework, explicit concrete type/ abstract type mappings for each type are usually provided in an XML or other configuration files. A 'code as configuration' framework carries out the same explicit mapping of each type in a central location in the application. Convention-based DI frameworks use a specialised set of container methods or helper classes (often called builders or installers) to carry out registrations.

The advantages of a convention-based framework are that you write less code to register components, and that the framework's container is decoupled from the components it installs. Convention-based installers have a lower management overhead than configuration approach. The only downside is that all developers need to know what the convention is – otherwise types will be mis-registered, or registered multiple times.

The endjin composition framework is a convention-based framework which carries out registrations using a range of installers:

  • Interface installer - installs all types in the assembly that inherit from a specified interface
  • Namespace installer – installs all types in a given namespace or partial namespace against their default interface
  • Named type installer – Installs services implementing a specified interface which provides a static property to determine the name against which they should be registered in the container.
  • Suffix installer  – installs all types where the type's name ends with a given suffix.

The installers use C# interface/ implementation naming conventions to identify which types use a particular interface.

Because a DI or composition framework deals with object lifecycle management as well as creation, another facet of registration is determining the lifecycle of the concrete types registered. Most DI frameworks allow you to specify whether a type should be transient or singleton. A transient object goes out of scope when the object using it goes out of scope. If a single object should persist and be re-used across the application, it should be registered as a singleton.

Camping trip dependencies

Registration alone is not enough to enable the DI framework to function. When a type is requested from the container, if that type requires any dependencies as constructor parameters, the DI framework must also be able to identify and create these dependencies, and any dependencies of these dependencies, and so on.

This means constructing 'dependency graph' – a data structure which records dependencies among a set of classes.

The dependency graph is constructed automatically through a process known as auto-wiring, which examines a class's constructor signature, and then the constructor signatures of its dependencies, and so on, until there are no more dependencies. This is where constructor injection comes in. Because constructor injection alters the class's constructor signature, the DI framework can extract information about each class's dependencies, before an instance has been created.

Constructor signatures will usually specify their parameters using abstract types, so the auto-wiring process needs to check the information it gathered at the registration stage about abstract type/concrete type mappings, before proceeding on the next dependency.

The point at which type discovery happens varies by framework. In the case of endjin's composition framework, it happens during the initial setup of the container.

Power BI Weekly is a collation of the week's top news and articles from the Power BI ecosystem, all presented to you in one, handy newsletter!

Resolution

The DI container exposes a method called Resolve(), GetInstance() or something similar, that returns an instance of the concrete object mapped to an abstraction, along with any dependencies needed to create that object. This is effectively the service locator pattern.

For example:

I'll use 'Resolve' when I'm talking about this type of method for the rest of this blog.

Saying that Resolve returns a concrete type for an abstraction is a simplification. Although an instance of a concrete type is returned, the Resolve method's contractual return type is either the abstract type provided, or simply 'object'. One of the strengths of DI frameworks is their ability to provide variations of the Resolve method supporting many different uses.

The endjin composition framework's IContainer interface defines several Resolve methods, which vary in their use of generics, accepted parameters and return types. It's possible to specify the abstract type using generics, to provide a Type object as a parameter, or to use a string as a parameter. There are also a variety of ResolveAll methods which return an IEnumerable.

How do dependency injection frameworks integrate with applications?

A DI framework should be integrated with the rest of the application in, or as close as possible to the 'composition root'. Mark Seemann's blog on the topic defines the composition root as 'a (preferably) unique location in an application where modules are composed together'.

DI containers should be created here. Code as configuration-based DI frameworks should explicitly map concrete types to abstractions here. Convention-based DI frameworks should carry out type registration here, using the installers to register types against abstractions en masse. Calls to resolve objects should also occur in the composition root where possible.

In practice, an application may contain more than one composition root – for example:

  • when resolving serializers for an object coming off disk
  • when resolving handlers for an HTTP request for a resource at an endpoint
  • at task worker roots.

Each of these additional composition roots represents a composition boundary in the system – an isolated part of the structure that cannot be known at application start up time.

Composition roots are found in different places in different types of application. The simplest example of a composition root is the main method in a Console application. For .NET MVC applications, it's in or near the Application_Start method in the Global.asax file.

This is where a DI framework can prove itself useful, by providing adaptations for different platforms.

As Mike explains in his blogs, endjin created a custom composition framework because we wanted a stripped down library which provided low-ceremony composition across all major Microsoft platforms, and dealt with bad Resolve requests efficiently. The framework consists of a set of core libraries with contracts and classes such as a container and installers, together with platform-specific packages providing specialised installers, bootstrapper classes, and configuration files. The framework supports:

  • .NET 4.0
  • .NET 4.5
  • WinRT
  • Windows Phone 8
  • .NET 4.0 MVC applications
  • .NET 4.5 MVC applications
  • .NET 4.0 Web API services
  • .NET 4.5 Web API services.

Questions

Here are some of the questions which I found myself asking as I learned about DI.

1. Does DI rule out the use of factories?

I described the ways in which DI differs from the use of factories earlier in this blog.

This is not to say that factories can't play a role in a DI framework.

The standard elements of a convention-based framework – installers and a container – can be augmented by a factory class which encapsulates any rules around the registration and resolution of types.

In the case of the endjin composition framework, an abstract ContentFactory class provides a model for creating factories which let you register a concrete type together with a ContentType string. When a request is made to retrieve an item from the factory, a search will be made for a type registered with that content type, or, if that's not found, a type registered for the next smallest substring of the ContentType, and so on. The content type fallback mechanism supports a RESTful architecture and allows you to specify a preferred encoding such as +json or +xml.

This helps you deal with more complex registrations and resolutions, for example where several concrete types use the same interface but deal with a different content type.

2. When multiple concrete types can be created from one interface, how does the DI container know which particular concrete type applies in which circumstance?

This a puzzle that had me scouring the internet. If multiple concrete types have the same interface, how would the DI framework know which concrete type to create and inject?

A DI framework gives developers control over which concrete types are mapped to which implementations through a configuration file or registration process, but what if different implementations of the same interface are needed by different parts of the application?

Where it is necessary to register multiple concrete types for an interface, a named type installer or an abstract factory can be used in combination with DI to allow distinguishable registrations of several concrete types against the same interface.

An abstract factory can also be used to deal with the related situation where the concrete type of the dependency required depends on a value calculated at runtime.

3. What happens when a class needs to calculate a value and pass it into its dependency?

The examples I've talked about have been straightforward cases where objects are created with other objects as constructor parameters, or, without parameters.

But how about where a class creates its dependency and provides it with some value it's calculated at the same time?

For example, imagine that somewhere in the application is an AlertManager class which does this:

This dependency can't be straightforwardly injected through the AlertManager's constructor, because an instance of the class needs to be running to provide the value localStoreCount to its dependency.

Dependency injection forces you to rethink object creation and control.

Firstly, rather than using a value type or string as a constructor parameter, it's better to add a layer of indirection and use an object encapsulating this calculation instead. So now in our example, the AlertManager creates two dependencies, and passes one to the other.

Still not ideal. Applying DI changes this all around. Responsibility for composing one class using another shouldn't lie with the AlertManager – this role is carried out by the DI framework, using the information provided by the application in the form of its constructor signatures.

One way of changing AlertManager to apply DI is to provide it with a PartnerAlert through its constructor. The constructor for PartnerAlert will specify that an INumberOfStores is required – and as an INumberOfStores no longer requires a value as an input parameter, the framework can create this when it resolves the objects in the application.

So we end up with a simplified version of the original class, which is no longer responsible for creating its dependencies:

4. Isn't there a risk that with DI that classes will have huge numbers of constructor parameters?

Applying a DI framework to an existing application might result in this happening:

This is bad practice because a dependency remains a dependency even if its injected into a class. The class is still coupled to its dependencies to some extent, and the more it has, the more tightly coupled the application becomes overall.

However, this problem isn't really caused by the DI pattern, it's caused by the way the application has been designed.

The Hadi Hariri blog Dealing with the too many dependencies problem suggests applying the single responsibility principle and using 'small focused classes' to avoid this problem, working around the limitations imposed by various frameworks where necessary.

Finally, I'm including a question I had before I really understood DI how a DI framework worked. I'm including it in case any other newcomers to DI use the same search terms that I did.

5. Doesn't the Resolve method rule break the pattern of constructor injection?

It had been hammered home in blog after blog that constructor injection was essential for DI, because it made each class's dependencies explicit, yet the Resolve method provided by DI frameworks seems to break this pattern. Resolve is called from inside a constructor, much like new was used before.

The answer to this apparent contradiction lies in understanding how a DI framework is applied to an application. It is perfectly acceptable – and in fact necessary - to use a Resolve method in the composition root – the part of the application responsible for starting the rest of the application - to create some core object used by the application. Something needs to kick the object creation process off!

However, the Resolve method should be used as close as possible to the composition root. Following the call to Resolve in one or more central locations in the application, the auto-wiring provided by DI frameworks uses the information available from constructor injection to create the core object's dependencies, their dependencies, and so on.

Alice Waddicor

Software Engineering Apprentice III

Alice Waddicor

Alice was the second person to complete our Apprenticeship programme.

Alice came from a technical writing background, and re-trained because of an interest in technology, particularly data processing, information extraction, and automation.

During her apprenticeship Alice became passionate about Tech for Good and became an environmental campaigner.

Alice worked at endjin between 2014-2017.