Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Inversion of Control: Overview with Examples

4.70/5 (27 votes)
8 May 2012CPOL7 min read 128.6K   957  
This article discusses the basic concepts of IoC, how to achieve it, and Dependency Injections.

Introduction

Definition: “Inversion of Control is an abstract principal describing an aspect of some software architecture design in which the flow of control of a system is inverted in comparison to procedural programming.”

What happens in procedural programming is a chunk of code that uses or consumes another chunk of code is in control of the process. It knows what piece of code, which method, in which class, it uses and this way it knows about the implementation details in the code it uses.

Example

Image 1

Here is an example of such a scenario when a class X uses another class Y. The consumer class X needs to consume the class Y to accomplish something. This is a very obvious example, but wait and think for a minute, does X really need to know that it uses Y? Isn’t it enough that X knows that it uses something that has the behavior, the methods, the properties, etc., of Y without knowing who actually implements the behavior? Extract the abstract definition of the behavior used by X in Y and let the consumer X to use an instance of that behavior instead of Y. This way X does the same thing without knowing the specifics about Y.

Image 2

As illustrated above, class Y implements interface I and class X uses I. An interesting thing is though class X still uses class Y through interface I, X doesn’t know that it uses Y, it just knows that it uses something that implements I. Right now, it’s Y, but it could be A, B, or C that implements interface I. This way we are reducing the dependency of X over Y. Now you might have a question in your mind, what is dependency? And how is it dangerous for software?

What is Dependency in terms of classes?

The example mentioned in the start of this article is an example of dependent classes. When class X uses a method of class Y or class Y as a whole, we can say that class X has some level of dependency over class Y. Dependency may extend to several levels. X uses Y, Y uses A and B, A uses C, and more. This way this chain goes on and on. The problem with this is if we have any change in these classes, it may spawn to multiple classes. The solution to this dependency problem is define a loose relation between classes. One way to achieve this is the Inversion of Control pattern. This pattern uses Dependency Injection to eliminate tight coupling between objects. Let’s see dependency with some practical example.

A sample scenario

We have a class called LoggerEngine which is used for logging messages. It has a method called Log which receives a string object as an argument and logs it into a file using another class named FileLogger.

Image 3

The code for LoggerEngine looks like this:

Image 4

Notice the above code in which the Log method creates an instance of the FileLogger class and then logs the message using its own Log method. Here you may define all three methods to be static or there might be a singleton instance ofthe FileLogger class. What matters is, the dependency of the LoggerEngine class over the FileLogger class. We can solve this by creating an abstract definition of the behavior in FileLogger that LoggerEngine needs in the form of an interface ILogger and making the Log method use an instance of ILogger instead of an instance of FileLogger.

Image 5

Now LoggerEngine no longer knows about the FileLogger class. It just uses an instance of the ILogger interface of which FileLogger is one of many possibly implementations. Now in fact we could easily change it so that LoggerEngine logs messages on the console instead of a file without changing a single line of code in LoggerEngine. This all sounds good, but the main question is how does LoggerEngine get an instance of ILogger? Here we need a way for LoggerEngine to get an instance of ILogger at runtime without knowing about the concrete implementation. There are a couple of ways to do that, one of which is through Dependency Injection.

Dependency Injection

This is the basic concept for implementing the Inversion of Control (IoC) pattern.

  • It eliminates tight coupling between objects.
  • Makes objects and application more flexible.
  • It facilitates creating more loosely coupled objects and their dependencies.

Image 6

Constructor Injection

Here the object reference would get passed to the constructor of the business class LoggerEngine. In this case since the LoggerEngine class depends on the FileLogger class, a reference of the FileLogger class will pass steps to implement Constructor Injection.

Step-1 Create an Interface:

C#
namespace IoCSample
{
    public interface ILogger
    {
        void OpenLog();
        void CloseLog();
        void Log(string message);
    }
}

Only a method of the interface will be exposed in the business class.

Step-2 Implement an interface to the FileLogger class.

C#
namespace IoCSample
{
    public class FileLogger : ILogger
    {
        StreamWriter streamWriter = null;
        public string LogFileName { get; private set; }
        public FileLogger(string logFileName) { LogFileName = logFileName; }
        public FileLogger() : this(@"C:\Windows\Temp\AppLog.log") { }

        public void OpenLog()
        {
            if (!Directory.Exists(Path.GetDirectoryName(LogFileName)))
                Directory.CreateDirectory(Path.GetDirectoryName(LogFileName));

            streamWriter = new StreamWriter(File.Open(LogFileName, FileMode.Append));
            streamWriter.AutoFlush = true;
        }

        public void CloseLog()
        {
            if (streamWriter != null)
            {
                streamWriter.Close();
                streamWriter.Dispose();
                streamWriter = null;
            }
        }

        public void Log(string message)
        {
            if (streamWriter != null)
            {
                streamWriter.WriteLine("------------------------------------------" + 
                                  "-----------------------------------------------");
                streamWriter.WriteLine(string.Format("Log Message : {0}", message));
                streamWriter.WriteLine(string.Format("Log Date    : {0:dd}/{0:MM}/{0:yyyy}", DateTime.Now));
                streamWriter.WriteLine(string.Format("Log Time    : {0:hh}:{0:mm}:{0:ss} {0:tt}", DateTime.Now));
                streamWriter.WriteLine("------------------------------------------" + 
                                  "-----------------------------------------------");
                streamWriter.WriteLine();
            }
        }
    }
}

An object of the FileLogger class is going to be referenced by the LoggerEngine class. So this class needs to implement the interface.

Step-3 Make a reference of the interface in the LoggerEngine class.

C#
namespace IoCSample.Constructor_Dependency_Injection
{
    public class LoggingEngine
    {
        private ILogger logger = null;

        public LoggingEngine(ILogger logger)
        {
            this.logger = logger;
        }

        public void Log(string message)
        {
            logger.OpenLog();
            logger.Log(message);
            logger.CloseLog();
        }
    }
}

Step-4 Create a third party class, which creates an instance of all these objects.

C#
namespace IoCSample
{
    public class IoCClass
    {
        ILogger logger = null;

        public string FileName { get; set; }

        public void WriteLogUsingConstructorInjection(string message)
        {
            logger = new FileLogger(FileName);

            Constructor_Dependency_Injection.LoggingEngine cEngine = 
                          new Constructor_Dependency_Injection.LoggingEngine(logger);
            cEngine.Log(message);
            cEngine = null;
        }
    }
}

Step-5 Use this third party class at the client side.

C#
namespace IoCSample
{
    class Program
    {
        static void Main(string[] args)
        {
            string fileName = @"C:\Windows\Temp\AppLog.log";

            IoCClass obj = new IoCClass() { FileName = fileName };

            // 1. Constructor Injection.
            obj.WriteLogUsingConstructorInjection("Hi, hetu how r u?");

            Console.Read();
        }
    }
}

Shortcoming of Constructor Injection

  • In constructor injection, the business logic class doesn’t have a default constructor.
  • Once the class is instantiated, object dependency cannot be changed.

Setter Injection

This uses properties to inject a dependency. Here rather than creating a reference and assigning it in the constructor, it is done in properties. This way the LoggingEngine class could have a default constructor.

Step-1 Same as Constructor Injection

Step-2 Same as Constructor Injection

Step-3 LoggerEngine.cs

C#
namespace IoCSample.Setter_Dependency_Injection
{
    public class LoggerEngine
    {
        public ILogger Logger { get; set; }

        public LoggerEngine() { }
        public LoggerEngine(ILogger logger) { Logger = logger; }

        public void Log(string message)
        {
            if (Logger != null)
            {
                Logger.OpenLog();
                Logger.Log(message);
                Logger.CloseLog();
            }
        }
    }
}

In the LoggerEngine class, there is a property logger, which sets and gets the value of reference of the interface.

Step-4 There are some changes in the third party class:

C#
namespace IoCSample
{
    public class IoCClass
    {
        ILogger logger = null;

        public string FileName { get; set; }

        public void WriteLogUsingSetterInjection(string message)
        {
            logger = new FileLogger(FileName);

            Setter_Dependency_Injection.LoggerEngine sEngine = 
                   new Setter_Dependency_Injection.LoggerEngine();

            sEngine.Logger = logger;
            sEngine.Log(message);

            sEngine = null;
        }
    }
}

Step-5 Same as Constructor Injection.

Advantages of Setter Injection

  • It's more flexible than Constructor Injection
  • Dependency of object can be changed without creating an instance
  • Dependency of object can be changed without changing constructor
  • Setters have constructive and self descriptive meaningful names that simplify understanding and using them

Interface Injection

In this, instance creation is transferred to an additional interface. This interface is passed as an argument in the calling method.

Step-1 Same as Constructor Injection

Step-2 Same as Constructor Injection

Step-3 Create an interface and its implemented class, which creates an instance.

C#
namespace IoCSample.Interface_Dependency_Injection
{
    public interface ILoggerInject
    {
        ILogger Construct();
    }

    public class FileLoggerInject : ILoggerInject
    {
        public string FileName { get; set; }
        public ILogger Construct()
        {
            return new FileLogger(FileName);
        }
    }
}

Here one more interface and its implemented class has been defined which are responsible for instance creation.

Step-4 LoggerEngine.cs

C#
namespace IoCSample.Interface_Dependency_Injection
{
    public class LoggerEngine
    {
        private ILogger logger;

        public void Log(ILoggerInject loggerInject, string message)
        {
            logger = loggerInject.Construct();
            logger.OpenLog();
            logger.Log(message);
            logger.CloseLog();
        }
    }
}

Step-5 Our third party class will be:

C#
namespace IoCSample
{
    public class IoCClass
    {
        ILogger logger = null;

        public string FileName { get; set; }

        public void WriteLogUsingInterfaceInjection(string message)
        {
            Interface_Dependency_Injection.LoggerEngine iEngine = 
                  new Interface_Dependency_Injection.LoggerEngine();

            Interface_Dependency_Injection.ILoggerInject loggerInject = 
                  new Interface_Dependency_Injection.FileLoggerInject() { FileName = FileName };

            iEngine.Log(loggerInject, message);
            loggerInject = null;

            iEngine = null;
        }
    }
}

Step-6 Same as Step-5 in Constructor Injection.

Service Locator Injection

Here a Service class is introduced which contains either a static method or a Service class may be used as in the Singleton pattern.

Step-1 Same as Constructor Injection.

Step-2 Same as Constructor Injection.

Step-3 Define service class LoggerService as follows:

C#
namespace IoCSample.Service_Dependency_Injection
{
    class LoggerService
    {
        public static ILogger Logger { private get; set; }

        public static ILogger getLoggerService()
        {
            if (Logger == null)
                Logger = new ConsoleLogger();
            return Logger;
        }
    }
}

Here we have a static write-only property Logger which is used for setting the value of reference of the ILogger instance and a static method which is used for getting the value of reference of the ILogger instance.

Step-4 LoggerEngine.cs:

C#
namespace IoCSample.Service_Dependency_Injection
{
    public class LoggerEngine
    {
        private ILogger logger;
        public void Log(string message)
        {
            logger = LoggerService.getLoggerService();
            logger.OpenLog();
            logger.Log(message);
            logger.CloseLog();
        }
    }
}

Step-5 Our third party class will be:

C#
namespace IoCSample
{
    public class IoCClass
    {
        ILogger logger = null;

        public string FileName { get; set; }

        public void WriteLogUsingServiceInjection(string message)
        {
            Service_Dependency_Injection.LoggerEngine slEngine = 
                    new Service_Dependency_Injection.LoggerEngine();

            Service_Dependency_Injection.LoggerService.Logger = new FileLogger(FileName);
            slEngine.Log(message);

            slEngine = null;
        }
    }
}

Step-6 Same as Step-5 in Constructor Injection.

Generic-Type Injection

In this method, either we can define a Generic LoggerEngine class or a Generic method in the LoggerEngine class. A Generic method would be more appropriate because if we use a Generic class then it will cause the same problems which are identified in Constructor Injection.

Step-1 Same as Constructor Injection.

Step-2 Same as Constructor Injection.

Step-3 Let’s define the LoggerEngine class which contains a Generic method for writing logs:

C#
namespace IoCSample.Generic_Dependency_Injection
{
    public class LoggerEngine        
    {
        public void Log<TLogger>(string message) where TLogger : ILogger, new()
        {
            ILogger logger = Activator.CreateInstance<TLogger>();
            logger.OpenLog();
            logger.Log(message);
            logger.CloseLog();
        }
    }
}

Here we explicitly mentioned that our TLogger would be a reference implementation of ILogger.

Step-4 Our third party class will be:

C#
namespace IoCSample
{
    public class IoCClass
    {
        ILogger logger = null;

        public string FileName { get; set; }

        public void WriteLogUsingGenericTypeInjection(string message)
        {
            Generic_Dependency_Injection.LoggerEngine gEngine = 
                    new Generic_Dependency_Injection.LoggerEngine();
            gEngine.Log<FileLogger>(message);
            gEngine = null;
        }
    }
}

Step-5 Same as Constructor Injection.

Benefits of Inversion of Controls

  • We can reduce the dependency between objects and can design more flexible systems.
  • We can isolate our code at the time of unit testing.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)