Click here to Skip to main content
15,867,765 members
Articles / Programming Languages / C#

Dependency Injection Pattern in C# – Short Tutorial

Rate me:
Please Sign up or sign in to vote.
4.91/5 (34 votes)
9 Aug 2022MIT16 min read 43.6K   380   87   13
We explain DI Pattern, DIP, IoC, DI Container
This is planned to be a concise tutorial on Dependency Injection Pattern and related topics: Dependency inversion principle (DIP), Inversion of control (IoC) principle, and Dependency Injection Container (aka IoC container). While short, this tutorial goes into enough breadth and depth to provide a solid overview of the topics. It is well suited for those that need to master basic concepts fast.

Interview Survival Tutorial

The goal of this article is to provide a short, concise tutorial about Dependency Injection Pattern and related topics. It can be used as “the first contact tutorial” for those wanting to learn about the topic or as a “refresher material” for those that want to refresh their knowledge. It can be used as interview preparation material for those that need to master basic concepts fast.

Topics covered in this tutorial are typically asked a candidate interviewing for a Senior (.NET) Developer position.

This is a basic tutorial and does not go into all the fine details. People learn best when presented with knowledge in “concentric circles”, in the first circle they are taught the basis of everything, in the second concentric circle they go over what they learned in the previous circle and extend that knowledge with more details, then in the next circle they do a similar thing again, etc. This article is meant to be the first pass on the topic for an interested reader.

Topics Presented

The following topics are presented:

  • Dependency Injection Pattern – DI (*)
  • Dependency inversion principle – DIP (**)
  • Inversion of control – IoC (***)
  • Dependency Injection Container (****)

Dependency Injection Pattern – DI (*)

First of all, “Dependency Injection Pattern” is a SOFTWARE DESIGN PATTERN. It is called a “pattern’ because it suggests low-level specific implementation to a specific problem.

The main problem this pattern aims to solve is how to create “loosely coupled” components. It does that by separating the creation of components from their dependencies.

There are four main roles (classes) in this pattern:

  1. Client: The client is a component/class that wants to use services provided by another component, called Service.
  2. Service-Interface: The service interface is an abstraction describing what kind of services Service component is providing.
  3. Service: The Service component/class is providing services according to Service-Interface description.
  4. Injector: Is a component/class that is tasked with creating Client and Service components and assembling them together.

The way it works is Client is dependent on Service-Interface IService. Client depends on IService interface but has no dependency on Service itself. Service implements IService interface and offers certain services that Client needs. Injector creates both Client and Service objects and assembles them together. We say that Injector “injects” Service into Client.

Here is a class diagram of this pattern:

Image 1

Here is a sample code of this pattern:

C#
public interface IService
{
    void UsefulMethod();
}

public class Service : IService
{
    void IService.UsefulMethod()
    {
        //some useful work
        Console.WriteLine("Service-UsefulMethod");
    }
}

public class Client
{
    public Client(IService injectedService = null)
    {
        //Constructor Injection
        _iService1 = injectedService;
    }

    private IService _iService1 = null;

    public void UseService()
    {
        _iService1?.UsefulMethod();
    }
}

public class Injector
{
    public Client ResolveClient()
    {
        Service service = new Service();

        Client client = new Client(service);

        return client;
    }
}

internal class Program
{
    static void Main(string[] args)
    {
        Injector injector = new Injector();

        Client cli = injector.ResolveClient();
        cli.UseService();

        Console.ReadLine();
    }
}

Types of Dependency Injection based on a Method of Injecting

Often in literature [1], one can find that they are mentioning different types of Dependency Injection, classified based on the method of injecting Service into Client. I think that is not such an important distinction since the effect is always the same, that is reference to Service is being passed to Client, no matter how. But, for completeness, let us explain it.

So, types of Dependency Injection are:

  1. Constructor Injection – Injection is done in Client constructor
  2. Method Injection – Injection is done via a dedicated method
  3. Property Injection – Injection is done via public property

Here is the code that demos each type:

C#
public interface IService
{
    void UsefulMethod();
}

public class Service : IService
{
    void IService.UsefulMethod()
    {
        //some useful work
        Console.WriteLine("Service-UsefulMethod");
    }
}

public class Client
{
    public Client(IService injectedService = null)
    {
        //1.Constructor Injection
        _iService1 = injectedService;
    }

    public void InjectService(IService injectedService)
    {
        //2.Method Injection 
        _iService1 = injectedService;
    }

    public IService Service
    {
        //3.Property Injection
        set { _iService1 = value; }
    }

    private IService _iService1 = null;

    public void UseService()
    {
        _iService1?.UsefulMethod();
    }
}

public class Injector
{
    public Client ResolveClient()
    {
        Service S = new Service();

        //NOTE: This is tutorial/demo code, normally you
        //implement only one of these three methods

        //1.Constructor Injection
        Client C = new Client(S);

        //2.Method Injection 
        C.InjectService(S);

        //3.Property Injection
        C.Service = S;

        return C;
    }
}

Main Point – Client Unaware of the Type of Service Injected

Let us emphasize the main thing in this design pattern. That is the fact that Client is completely ignorant of the type of Service injected, it just sees interface IService and has no clue what version of Service is being injected. Let us look at the following class diagram:

Image 2

Client has no knowledge of which service is being injected, if it is Service1, Service2 or Service3. That is what is wanted, we see that components/classes Client, Service1, Service2, and Service3 are “loosely coupled”.

Client class is now more reusable and testable. One typical usage of this feature is that in the production environment, Client is injected with real service Service1, and in the test, environment Client is injected Service2 which is a Mock service created just for testing.

Benefits of this Pattern

The benefits of this pattern are:

  • Creation of loosely coupled components/classes Client and Service
  • Client has no dependency nor knowledge of Service which makes it more reusable and testable
  • Enables parallel development of components/classes Client and Service by different developers/teams since the boundary between them is clearly defined by the IService interface
  • It eases the unit-testing of components

Disadvantages that this pattern brings are:

  • More effort to plan, create and maintain an interface
  • Dependency on Injector to assemble components/classes

Similar Patterns

This pattern is very similar to GoF book Strategy Pattern [2]. The class diagram is practically the same. The difference is in intent:

  1. Dependency injection is more like Structural Patten that has the purpose to assemble loosely coupled components and once assembled, they usually stay that way during Client lifetime; while
  2. Strategy pattern is Behavior Pattern that has the purpose to offer different algorithms to the problem which are usually interchangeable during Client lifetime.

Dependency Inversion Principle – DIP (**)

So, the “Dependency inversion principle (DIP)” is a SOFTWARE DESIGN PRINCIPLE. It is called “principle” because it provides high-level advice on how to design software products.

DIP is one of five design principles known under the acronym SOLID [3] promoted by Robert C. Martin [5]. The DIP principle states:

  1. High-level modules should not depend on low-level modules. Both should depend on the abstraction.
  2. Abstractions should not depend on details. Details should depend on abstractions.

Interpretation is:

While high-level principle talks about “abstraction”, we need to translate that into terms in our specific programming environment, in this case, C#/.NET. Abstractions in C# are realized by interfaces and abstract classes. When talking about “details”, the principle means “concrete implementations”.
So, basically, that means that DIP promotes the usage of the interfaces in C# and concrete implementations (low-level modules) should depend on interfaces.

Traditional module dependencies look like this:

Image 3

DIP proposes this new design:

Image 4

As you can see, some dependencies (arrows) have inverted directions, so that is where the name “inversion” comes from.

The goal of DIP is to create “loosely coupled” software modules. Traditionally, high-level modules depend on low-level modules. DIP has a goal to make high-level modules independent of low-level modules’ implementation details. It does that by introducing an “abstract layer” (in the form of an interface) between them.

Dependency Injection Pattern (*) follows this principle and is often mentioned as closely related to DIP realization. But the DIP principle is a broader concept and has an influence on other design patterns. For example, when applied to the Factory design pattern or Singleton design pattern, it suggests that those patterns should return a reference to an interface, not a reference to an object.

Inversion of Control – IoC (***)

Again, “Inversion of control (IoC)” is a SOFTWARE DESIGN PRINCIPLE. It is called “principle” because it provides high-level advice on how to design software products.

In traditional programming, a custom code always has flow control and calls libraries to perform tasks.
The IoC principle proposes that (sometimes) flow of control be given to the libraries (“framework”) which will call custom code to perform tasks.

When they say “framework”, they mean a specialized, arbitrary complex reusable module/library that is designed for a specific task, and custom code is written in a manner so it can work with that “framework”. We say that the “flow of control is inverted” since now “framework” calls into custom code.

The framework plays the role of the main program in controlling application activity. The main control of the program is inverted, moved away from you to the framework. Inversion of control is a key part what makes a framework different from a library ([26]).

The IoC principle promotes the development and usage of reusable “software frameworks” that implement common scenarios. Then, problem-specific custom code is written and made to work together with the “framework” to solve a specific task.

While IoC principle is often mentioned in the context of Dependency Injection Pattern (*) which follows it, it is a much broader concept. For example, an “UI framework” based on event handlers/callback methods also follows IoC principle. See [26], [25], [8] for more explanation.

Dependency Injection Pattern (*) follows this principle since the normal traditional approach is for Client to create Service and establish dependency. Here control is inverted, that is the creation of Service and the creation of dependency are delegated to the Injector, which in this case is the “framework”.

Dependency Injection Container (****)

So, “Dependency Injection Container (DI Container)” is a SOFTWARE MODULE/LIBRARY that enables automatic Dependency Injection with many advanced options.

In the terminology of IoC principle (***), DI Container has the role of the “framework” so often you will see it referred to as “DI framework”, but my opinion is that the “framework” word is overused and it leads to confusion (you have ASP MVC framework, DI framework, Entity Framework, etc.).

Often in literature, it is referred to as “IoC Container”, but I think IoC principle (***) is a broader concept than the DI pattern (*), and here we are taking really of the DI pattern implementation on a large scale. So, “DI Container” is a much better name, but the name “IoC Container” is very popular and is broadly used for the same thing.

What is DI Container

Remember DI pattern (*) and the role of the Injector? So, DI Container is an advanced module/library that serves as an Injector for many Services at the same time. It enables the implementation of DI pattern on a large scale, with many advanced functions. DI Containers are a very popular architectural mechanism and many popular frameworks such as ASP MVC plan for and enable the integration of DI Containers.

The most popular DI Containers are Autofac [10], Unity [15], Ninject [16], Castle Windsor [17], etc.

DI Container functions

Typical functions that one DI Container will offer are:

Register Mappings. You need to tell the DI Container mappings between abstraction (interfaces) to concrete implementations (classes) so that it can properly inject proper types. Here, you feed the container with basic info it needs to work.

Mange objects Scope and Lifetime. You need to tell the container what scope and lifetime will object it creates have.
Typical “lifestyle” patterns are:

  1. Singleton: A single instance of the object is always used.
  2. Transient: Every time a new instance of an object is created.
  3. Scoped: That is typically a singleton pattern per an implicitly or explicitly defined scope.

You need to tell the container for example if you want every time it resolves dependency a new object to be created or if you want a singleton pattern applied. And singleton can be, for example, per process, per thread, or per “user-defined scope”. Also, you need to specify the desired lifetime of your object. You can configure, for example, object lifetime per process, or object lifetime to be per “user-defined scope”, meaning the object will be disposed of at the end of the scope that the user defines. The container can enforce all that, just needs to be precisely configured.

Resolve Method. Here is the actual work of creating and assembling the required object/type being done. The container creates an object of the specific type, resolves all the dependencies, and injects them into the created object. The method works recursively into depth until all the dependencies are resolved. DI Container does the work of resolving the dependencies by using technologies like Reflection and similar.

DI Container: Autofac – Simple Example

One of the most used DI Containers in C# world is Autofac [10]. We will show a simple example of how it works.

Here is our class diagram:

Image 5

And here is our example code:

C#
public interface IService
{
}

public class Service : IService
{
    public Service()
    {
    }
}

public class Client
{
    public IService iService;

    public Client(IService injectedService)
    {
        iService = injectedService;
    }
}

internal class Program
{
    public static string MySerialize(object obj)
    {
        string serialized = null;
        var indented = Newtonsoft.Json.Formatting.Indented;
        var settings = new JsonSerializerSettings()
        {
            TypeNameHandling = TypeNameHandling.All
        };
        serialized = JsonConvert.SerializeObject(obj, indented, settings); //(5)
        return serialized;
    }

    static void Main(string[] args)
    {
        // Register mappings
        var builder = new Autofac.ContainerBuilder();
        builder.RegisterType<Service>().As<IService>(); //(1)
        builder.RegisterType<Client>().AsSelf();   //(2)
        Autofac.IContainer Container = builder.Build();    //(3)

        //Resolve object
        var client = Container.Resolve<Client>();     //(4)  

        // Json serialize object to see what we got
        Console.WriteLine(MySerialize(client)); //(6)

        Console.ReadLine();
    }
}

And here is the execution result:

Image 6

As you can see, Autofac has its own API that we need to follow. At (1), we registered the mapping IService->Service. Then in (2), we registered the Client itself. At (3), we build the container and is ready for use. At (4), we do resolution and that is where the resolution of dependencies and injection is done.
In order to verify that we got an object we wanted, we serialize it at (5) and print it out at (6).
If you look again at (*) and terminology there, then our class Client has the role of “Client” from (*), class Service has the role of “Service” from (*), and object Container has the role of “Injector” from (*).

Just a short note. This is a tutorial – demo of concept code. The manner in which we used DI Container in the above example, by explicitly requesting dependencies resolution on the top level, makes it a bit resemble Service Locator Pattern [29]. The proper usage of DI Container is to use it as a framework, and not to explicitly request resolution of dependencies.

DI Container: Autofac – Deep Dependency Example

Now we will show a more complicated example with a deep dependency tree. Here is the new class diagram.

Here is the new class diagram:

Image 7

And here is our example code:

C#
public class C
{
    public IS obj_is = null;
    public IT obj_it = null;

    public C(IS injectIs, IT injectIT)
    {
        obj_is = injectIs;
        obj_it = injectIT;
    }
}

public interface IS
{
}

public class S : IS
{
    public IU obj_iu = null;
    public IV obj_iv = null;

    public S(IU injectIU, IV injectIV)
    {
        obj_iu = injectIU;
        obj_iv = injectIV;
    }
}

public interface IT
{
}

public class T : IT
{
    public IZ obj_iz = null;

    public T(IZ injectIZ)
    {
        obj_iz = injectIZ;
    }
}

public interface IU
{
}

public class U : IU
{
}

public interface IV
{
}

public class V : IV
{
    public IX obj_ix = null;

    public V(IX injectIX)
    {
        obj_ix = injectIX;
    }
}

public interface IZ
{
}

public class Z : IZ
{
}

public interface IX
{
}

public class X : IX
{
}

internal class Program
{
    public static string MySerialize(object obj)
    {
        string serialized = null;
        var indented = Newtonsoft.Json.Formatting.Indented;
        var settings = new JsonSerializerSettings()
        {
            TypeNameHandling = TypeNameHandling.All
        };
        serialized = JsonConvert.SerializeObject(obj, indented, settings);
        return serialized;
    }

    static void Main(string[] args)
    {
        // Register mappings
        var builder = new Autofac.ContainerBuilder();
        builder.RegisterType<S>().As<IS>();
        builder.RegisterType<T>().As<IT>();
        builder.RegisterType<U>().As<IU>();
        builder.RegisterType<V>().As<IV>();
        builder.RegisterType<Z>().As<IZ>();
        builder.RegisterType<X>().As<IX>();
        builder.RegisterType<C>().AsSelf();
        Autofac.IContainer Container = builder.Build();

        //Resolve object
        var c = Container.Resolve<C>();

        // Json serialize object to see what we got
        Console.WriteLine(MySerialize(c));

        Console.ReadLine();
    }
}

And here is the execution result:

Image 8

As it can be seen from the execution result, Autofac DI Container did its work again. Please note that, for example, class S is, in terms of terminology of DI Pattern (*) at the same time “Client” and “Service”. It is a “Service” for class C, and a “Client” for classes U and V. Object Container plays the role of the “Injector”, in the terminology of (*)

Again, a short note. This is a tutorial – demo of concept code. The manner in which we used DI Container in the above example, by explicitly requesting dependencies resolution on the top level, makes it a bit resemble Service Locator Pattern [29]. The proper usage of DI Container is to use it as a framework, and not to explicitly request resolution of dependencies.

DI Container: Autofac – Configuring Object Scope and Lifetime

If we look at the above example, one question appears. If we have two objects of class C, objects c1 and c2, that were generated by DI Container through resolution, are these objects different or the same? And what about dependent objects, like objects of class T, let’s call them t1 and t2, are they all different or the same?

C#
//let us assume we created two objects, c1 and c1
var c1 = Container.Resolve<C>();
var c2 = Container.Resolve<C>();

//are they same or different?
//what will this give us as result?
bool sameObjects=(c1 == c2);  

The answer is: that is configurable. But since we didn’t configure that in the previous example, we will get the default behavior, that is every time is created a new object. In this case objects, c1 and c1 are different as are all dependent objects of classes S, T, U, V, Z, and X.

Typical options for configuring object scope and lifetime for DI Container Autofac ([11], [12]) are:

  1. Instance Per Dependency: Also frequently called in literature “transient”. This is an “every time new object” pattern. Basically, it means every time an object is requested, a new instance is created. That is the default behavior.
  2. Single Instance: Also known as “singleton”. That is basically “singleton per process”. Every time in the process you request resolution, you will get the same instance of the object.
  3. Instance Per Lifetime Scope: That is a “singleton per user-defined scope”. The user needs to specify the scope, and inside it, he will get the same instance all the time.
  4. Instance Per Matching Lifetime Scope: This is again singleton, but this time “singleton per user-defined named scope”. The user needs to use defined named scope, and every time inside it will get the same instance of the object.
  5. Instance Per Request: In ASP type of applications, it results in “singleton per request”. This is really the same as 4, just named scope is created per each request. More explanation at [12].
  6. Instance Per Owned: This is a bit complicated, so we will not go into details here. More explanation at [12].
  7. Thread Scope: This does not exist as a separate configuration option, but instead relies on 3 to create a named scope in your thread method and implement it as an “Instance Per Lifetime Scope” solution.

Here are some examples of how the configuration options listed above look in the code:

C#
//1. Instance Per Dependency
builder.RegisterType<Worker>(); // using defualt behaviour
//explicit definition
builder.RegisterType<Worker>().InstancePerDependency(); 

//2. Single Instance
builder.RegisterType<Worker>().SingleInstance();

//3. Instance Per Lifetime Scope
builder.RegisterType<Worker>().InstancePerLifetimeScope();

using (var scope1 = container.BeginLifetimeScope())
{
    var w1 = scope1.Resolve<Worker>();
    var w2 = scope1.Resolve<Worker>();
    // w1 and w2 are the same instance 
}

//4. Instance Per Matching Lifetime Scope
builder.RegisterType<Worker>()
    .InstancePerMatchingLifetimeScope("MyScope");

using (var scope1 = container.BeginLifetimeScope("MyScope"))
{
    var w1 = scope1.Resolve<Worker>();
}

using (var scope2 = container.BeginLifetimeScope("MyScope"))
{
    var w2 = scope2.Resolve<Worker>();
    // w1 and w2 are the same instance 
}

//5. Instance Per Request
builder.RegisterType<Worker>().InstancePerRequest();

//6. Instance Per Owned
builder.RegisterType<OwnerClass>();
builder.RegisterType<SlaveClass>().InstancePerOwned<OwnerClass>();

//7. Thread Scope
//similar to 3.

We will not go into more detail or provide code samples because that would be too much for this article.

DI Container – Integration with Application Frameworks

DI Containers are a very popular architectural mechanism and many application frameworks plan for and enable integration with DI Containers.

For example, ASP.NET MVC framework is exposing the interface IDependencyResolver [13] that the prospect DI Container needs to implement. An example of integration with Autofac looks like this:

C#
// ASP.NET MVC and Autofac integration
// Context:
// - build Autofac DI Container
//      var builder = new Autofac.ContainerBuilder();
//      Autofac.IContainer container = builder.Build();
// - container implements interfaces Autofac.IContainer  
//      and Autofac.IComponentContext
// - new AutofacDependencyResolver(container) implements 
//      System.Web.Mvc.IDependencyResolver
// - System.Web.Mvc provides a registration point for 
//      dependency resolvers with method
//      public static void System.Web.Mvc.DependencyResolver
//      .SetResolver(System.Web.Mvc.IDependencyResolver resolver)
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

So, the point is ASP.NET MVC framework provides a registration point for dependency resolver. If you do not want to use DI, that is fine too. But, if you want to use DI in your ASP.NET MVC application, you can register your DI Container of choice with the application framework as shown above, and magically DI resolution will start to work in your application.

For information on how to integrate the Autofac DI container with other applications, see [27]. This is enough material for this tutorial article.

Conclusion

In this article, we focused on the Dependency Injection Pattern (DI) and its industrial application Dependency Injection Container (aka IoC Container). We also explained related principles for software design, the Dependency Inversion Principle (DIP) and Inversion of Control (IoC) principle. We showed some sample code using Autofac container. Emphasis is on the reader’s ability to understand and appreciate the practical usage of DI Container in modern applications.

DI Pattern and DI Container are mainstream technologies of Software Architecture today and are here to stay.

In this tutorial, we gave a concise and brief overview of the material suitable for the reader that needs to master concepts fast. Further reading on the topics is recommended. Some beginner’s tutorials are [14], [18] – [23]. Serious literature is [9], [8], [26].

References

History

  • 1st June, 2022: Initial version

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer
Serbia Serbia
Mark Pelf is the pen name of just another Software Engineer from Belgrade, Serbia.
My Blog https://markpelf.com/

Comments and Discussions

 
GeneralMy vote of 5 Pin
DiponRoy15-Aug-22 18:28
mvaDiponRoy15-Aug-22 18:28 
GeneralMy vote of 5 Pin
Abbott Fleur 202211-Aug-22 8:14
Abbott Fleur 202211-Aug-22 8:14 
QuestionVery good article, but... Pin
Paulo Zemek22-Jun-22 13:55
mvaPaulo Zemek22-Jun-22 13:55 
PraiseDI container defaults and options hell Pin
Jeltz12-Jun-22 13:00
Jeltz12-Jun-22 13:00 
GeneralRe: DI container defaults and options hell Pin
Mark Pelf 2-Jun-22 19:00
mvaMark Pelf 2-Jun-22 19:00 
PraiseRe: DI container defaults and options hell Pin
wmjordan4-Jun-22 15:29
professionalwmjordan4-Jun-22 15:29 
GeneralRe: DI container defaults and options hell Pin
John Brett 20217-Aug-22 23:25
John Brett 20217-Aug-22 23:25 
QuestionResolving by Interface Pin
rafik19822-Jun-22 6:38
rafik19822-Jun-22 6:38 
GeneralRe: Resolving by Interface Pin
Mark Pelf 2-Jun-22 10:26
mvaMark Pelf 2-Jun-22 10:26 
QuestionI might be too focused on coding style, but... Pin
Paulo Zemek1-Jun-22 22:56
mvaPaulo Zemek1-Jun-22 22:56 
PraiseRe: I might be too focused on coding style, but... Pin
Mark Pelf 2-Jun-22 20:52
mvaMark Pelf 2-Jun-22 20:52 
GeneralRe: I might be too focused on coding style, but... Pin
Graeme_Grant9-Aug-22 13:52
mvaGraeme_Grant9-Aug-22 13:52 
PraiseRe: I might be too focused on coding style, but... Pin
Mark Pelf 9-Aug-22 20:36
mvaMark Pelf 9-Aug-22 20:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.