Dependency Injection / Inversion of Control–A Concrete Example-Roll your own

 

Dependency Injection / Inversion of Control–A Concrete Example-Roll your own

Rate This
  • Comments 6
 Who’s the wizard behind the curtain?

I’ve been reading Martin Fowler’s post about Inversion of Control / Dependency Injection at http://www.martinfowler.com. I looked long and hard for very simple examples that were easy to follow. I wanted to explore how they work, not just how to use an existing library.

How exactly does Dependency Injection work? It seemed rather mysterious to me from the implementation point of view. Some people look at a compiler and think, “Wow, that’s cool how it could translate source code into machine code.”

I’m the type of person that wants to know how exactly it works.

So in this post I show you how you might start to think about building a Dependency Injection container.   

I focus on code here. If you want to read all the friendly narrative, here is Martin’s post:

Inversion of Control Containers and the Dependency Injection pattern http://martinfowler.com/articles/injection.html
The difficult way to learn how things work – using pre-built libraries

But the basic guidance is to use pre-built libraries. Microsoft historically supported the enterprise library, Unity. There is also Castle Windsor as an available library.

The Unity Application Block http://msdn.microsoft.com/en-us/library/ff648512.aspx

Castle Windsor http://www.castleproject.org/castle/download.html
Roll your own
But I was curious. What could I create in C# that mimicked the basic principles using some of features of C# just to illustrate the basic point with a simple, concrete example?
I’m borrowing heavily from Martin Fowler here, since he is highly regarded in software architecture space.
The definition in 2 sentences
In the Dependency Injection pattern, this decision is delegated to the "injector" which can choose to substitute different concrete class implementations of a dependency contract interface at run-time rather than at compile time. Being able to make this decision at run-time rather than compile time is the key advantage of dependency injection.
Conventional OO – The dependent object is in control
With conventional software development the dependent object decides for itself what concrete classes it will use.
Referring to Figure 1, this means the constructor for TextFileLister decides what concrete classes it will use.
The dependent object will typically choose from among the two classes in Figure 2
Figure 2 demonstrates that TextFileLister could choose either CommaTextFileReader or TabTextFileReader to process and read text files.

This is where the term Dependency Injection comes into play.

Figure 1 – The Dependency Injection Diagram

utu15abf

Figure 2 – The interface and the corresponding implementation

rnoxfijc

Let the assembler (MutableContainer) make the decision

The basic idea of the Dependency Injection sample we are writing is to have a separate object, an assembler, that populates a field in the TextFileLister class with an appropriate implementation for the IFileReader interface, resulting in a dependency diagram along the lines of Figure 1.

In a nutshell, the assembler makes the call. It chooses either CommaTextFileReader or TabTextFileReader. The key point is that TextFileLister doesn’t make the decision.

Figure 3 illustrates the Visual Studio project I created to demonstrate the points.

Figure 3 – Visual Studio

ln4vue31

CommaDelimitedData.txt Contains a comma-delimited list of strings
CommaTextFileReader.cs Reads and parses CommaDelimitedData.txt, using commas as the delimeter.
IFileReader.cs The interface to CommaTextFileReader and TabTextFileReader
MutableContainer.cs The assembler which will build and configure objects at runtime. In a nutshell, it can be used to use either CommaTextFileReader or TabTextFileReader. This is the secret sauce to dependency injection.
Program.cs The main driver program containing main()
TabDelimitedData.txt Contains a tab-delimited list of strings
TabTextFileReader.cs Reads and parses TabDelimitedData.txt, using tabs as the delimeter
TextFileLister.cs The dependent object that uses the interface IFileReader. The TextFileLister will rely on CommaTextFileReader or TabTextFileReader, depending what the assembler decides (MutableContainer)
The code
Let’s start to explore the code. We’ll start with the basic objects and work our way up to the assembler.
Figure 4 is pretty straightforward and shows us the interface used by the two implementing classes in Figures 5 and 6. This is critical that we have an interface. Interfaces allow us to swap out the implementations without runtime or compile time errors.
Figure 4: IFileReader.cs - The interface to CommaTextFileReader and TabTextFileReader
using System;
namespace IocForDummies
{
    interface IFileReader
    {
        void Close();
        int CountColumns();
        int CountLines();
        void FillData(string[,] data);
        bool HasData();
        void ReadLine(string s);
    }
}

This is one of the two implementation files that use IFileReader. The code is self-explanatory. It opens a text file and parses it out. In this case it is simply parsing a comma-delimited text file.

Figure 5: CommaTextFileReader.cs - Reads and parses CommaDelimitedData.txt, using commas as the delimeter

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace IocForDummies
{
    public class CommaTextFileReader : IocForDummies.IFileReader
    {
        FileStream fsReader = null;
        StreamReader streamReader = null;

        public CommaTextFileReader(string path)
        {
            fsReader = File.Open(path, FileMode.Open);
            streamReader = new StreamReader(fsReader, System.Text.Encoding.ASCII);

        }
        public void ReadLine(string s)
        {
            s = streamReader.ReadLine();
        }
        public bool HasData()
        {
            return (streamReader.EndOfStream == false);
        }
        public void FillData(string[,] data)
        {
            string[] s = null;
            for (int i = 0; i < data.GetLength(0); i++)
            {
                s = streamReader.ReadLine().Split(',');
                for (int j = 0; j < data.GetLength(1); j++)
                {
                    if (s[j] != "")
                        data[i, j] = s[j];
                }
            }
        }
        public int CountLines()
        {
            int i = 0;
            string s = null;
            while ((s = streamReader.ReadLine()) != null)
            {
                i++;
            }
            fsReader.Seek(0, SeekOrigin.Begin);
            streamReader.DiscardBufferedData();
            return i;
        }
        public int CountColumns()
        {
            string s = streamReader.ReadLine();
            string[] columns = s.Split(',');
            fsReader.Seek(0, SeekOrigin.Begin);
            streamReader.DiscardBufferedData();
            return columns.Length;
        }
        public void Close()
        {
            streamReader.Close();
            fsReader.Close();
        }
    }
}

  
TabTextFileReader
This is the second of the two implementation files that use IFileReader. The code is self-explanatory. It opens a text file and parses it out. In this case it is simply parsing a tab-delimited text file.
Figure 6: TabTextFileReader.cs - Reads and parses TabDelimitedData.txt, using tabs as the delimeter
public class TabTextFileReader : IocForDummies.IFileReader
{
    FileStream fsReader = null;
    StreamReader streamReader = null;

    public TabTextFileReader(string path)
    {
        fsReader = File.Open(path, FileMode.Open);
        streamReader = new StreamReader(fsReader);

    }
    public void ReadLine(string s)
    {
        s = streamReader.ReadLine();
    }
    public bool HasData()
    {
        return (streamReader.EndOfStream == false);
    }
    public void FillData(string[,] data)
    {
        string[] s = null;
        for (int i = 0; i < data.GetLength(0); i++)
        {
            s = streamReader.ReadLine().Split(',');
            for (int j = 0; j < data.GetLength(1); j++)
            {
                if (s[j] != "")
                    data[i, j] = s[j];
            }
        }
    }
    public int CountLines()
    {
        int i = 0;
        string s = null;
        while ((s = streamReader.ReadLine()) != null)
        {
            i++;
        }
        fsReader.Seek(0, SeekOrigin.Begin);
        streamReader.DiscardBufferedData();
        return i;
    }
    public int CountColumns()
    {
        string s = streamReader.ReadLine();
        string[] columns = s.Split(',');
        fsReader.Seek(0, SeekOrigin.Begin);
        streamReader.DiscardBufferedData();
        return columns.Length;

    }
    public void Close()
    {
        streamReader.Close();
        fsReader.Close();
    }
}

The class in Figure 7 (TextFileLister) simply calls the code in the implementation files in Figures 5 and 6. The key point is IFileReader can contain either CommaTextFileReader or TabTextFileReader, depending on the assembler’s wishes. This is the key point in this post – that TextFileLister doesn’t determine the classes it will use.

Figure 7: TextFileLister.cs - The dependent object that uses the interface IFileReader. The TextFileLister will rely on CommaTextFileReader or TabTextFileReader, depending what the assembler (MutableContainer) decides

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace IocForDummies
{
    class TextFileLister
    {
        IFileReader reader = null;
        string[,] data = null;

        public TextFileLister(IFileReader fileReader)
        {
            reader = fileReader;
        }
        public void ReadData()
        {
            int lines = reader.CountLines();
            int columns = reader.CountColumns();
            data = new string[lines, columns];
            reader.FillData(data);
        }
        public void ShowData()
        {
            for (int i = 0; i < data.GetLength(0); i++) 
            {
                for (int j = 0; j < data.GetLength(1); j++)
                    Console.Write(string.Format("[{0}]", data[i, j]));
                Console.WriteLine("");
            }
        }
        ~TextFileLister()
        {
            reader.Close();

        }
    }
}

The Assembler – Where the magic happens
Figure 8 has some tricky code. The _typeToCreateCode field is a dictionary. The dictionary contains object creation code that is mapped to an object type. For example, we can ask the MutableContainer code to go and retrieve the previously added constructor code for any type. Figure 9 illustrates how we add entries to the Dictionary _typeToCreateCode field.
Using lamdas, it is very easy to pass in a type and a delegate. For example, the AddComponent(CreateCode createCode) gets called in Program.cs, where a type and some object construction code gets passed in.
The code in Figure 8 allows you to later retrieve an object type and it’s creation code at runtime, supporting the whole Dependency Injection paradigm. The MutableContainer code below is at the heart of the dependency injection capabilities.
Notice that AddComponent() will replace an existing object, not necessarily always add one. This is important because mutableContainer.AddComponent could either pass in CommaTextFileReader or TabTextFileReader, but not both.
Figure 8: MutableContainer.cs - The assembler which will build construct objects at runtime. In a nutshell, it can be used to use either CommaTextFileReader or TabTextFileReader
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace IocForDummies
{
    public class MutableContainer
    {
        public delegate object CreateCode();

        private readonly Dictionary<Type, CreateCode> _typeToCreateCode
                        = new Dictionary<Type, CreateCode>();

        public T Create<T>()
        {
            // Do a look up in the dictionary. Use the object type to do the lookup "typeof(T)".
            // The lookup will yield the object creation code.
            // Execute the object creation code.
            return (T)_typeToCreateCode[typeof(T)]();
        }
        internal void AddComponent<T>(CreateCode CreateCode)
        {
            // Remove previous entry, if it exists
            if (_typeToCreateCode.ContainsKey(typeof(T)))
                _typeToCreateCode.Remove(typeof(T));
            // Add the new entry
            _typeToCreateCode.Add(typeof(T), CreateCode);
        }
    }
}

Using MutableContainer
This is the code where the assembler is called to perform its work.
The key point of Dependency Injection is that the assembler determines how the TextFileLister gets constucted, whether TextFileLister uses CommaTextFileReader or TabTextFileReader. TextFileLister does not determine how it reads text files.
Figure 9: Program.cs – The main driver loop which illustrates our points
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace IocForDummies
{
    class Program
    {
        static void Main(string[] args)
        {
            string commaFileName = Environment.CurrentDirectory +
                                        @"..\..\..\" + @"CommaDelimitedData.txt";

            string tabFileName = Environment.CurrentDirectory +
                                        @"..\..\..\" + @"TabDelimitedData.txt";


            MutableContainer mutableContainer = new MutableContainer();
            
            mutableContainer.AddComponent<IFileReader>(() =>
            {
                // Embeded object is of type CommaTextFileReader
                IFileReader fileReader= new CommaTextFileReader(commaFileName);
                return fileReader;
            });

            mutableContainer.AddComponent<TextFileLister>(() =>
            {
                IFileReader fileReader = mutableContainer.Create<IFileReader>();
                return new TextFileLister(fileReader);
                
            });

            TextFileLister customCommaDataReader = mutableContainer.Create<TextFileLister>();
            customCommaDataReader.ReadData();
            customCommaDataReader.ShowData();

            // Now read a tab delimited file
            mutableContainer.AddComponent<IFileReader>(() =>
            {
                // Embeded object is of type TabTextFileReader
                IFileReader fileReader = new TabTextFileReader(tabFileName);
                return fileReader;
            });

            TextFileLister customTabDataReader = mutableContainer.Create<TextFileLister>();
            customTabDataReader.ReadData();
            customTabDataReader.ShowData();
        }
        }
}

Figure 10 – The text files

CommaDelimitedData.txt

row1col1,row1col2,row1col3

row2col1,row2col2,row2col3

row3col1,row3col2,row3col3

TabDelimitedData.txt

row1col1[tab]row1col2[tab]row1col3

row2col1[tab]row2col2[tab]row2col3

row3col1[tab]row3col2[tab]row3col3

* Note that [tab] is there instead of real tabs because they are invisible.

Conclusion
What I just described is Constructor Injection. There are 2 other types of injection, Setter Injection and Interface Injection. Martin Fowler describes the other forms of DI in at his blog.

Download for Azure SDK


  • Bruno, as always... excellent article. You do a great job of taking a complex topic and trimming it down to understandable and applicable parts. It's easy to create a demo that is overly complex, and you don't do that - thanks.

    As you know, IoC techniques like this are becoming more common; I mean really common (and MEF encourages it). From a testing point of view I understand the motivation. It does not make testing possible, it just makes testing easier (in a way). From a reuse point of view I understand the motivation. It does not make reuse possible, but it makes it easier (in a way).

    However, I think we could all agree that IoC in general is a complexity multiplier. And, I think we could all agree that complexity in general decreases maintainability, hand-off, and even performance (sometimes the perf impact is not trivial). The frameworks and scaffolding for IoC are not trivial, and require a "clear mind" to get just right.

    Now that you have explained IoC, would you also recommend it?

    And a question like that needs some qualifications. Because yes is correct in some situations and no is correct in other situations. You would not blog on a technique you would never recommend, I expect. So the real question is, what are the necessary qualities of a project in order to justify IoC/DI? Is it scope, is it purpose, is it team, is it budget... what should we consider?

    // Jerry Nixon

  • Great site. A lot of useful information here. I’m sending it to some friends!

  • Indeed a good explanation. I also like example given in <a href="javarevisited.blogspot.com.au/.../inversion-of-control-dependency-injection-design-pattern-spring-example-tutorial.html">IOC and DI with real world example</a>

  • Great explanation.

    Thanks

  • OMG, don't use this in a real world program.  It wil slow your app by x4 and adds Code bloat to the application.  Plus make it harder to debug.

    The spirit of the article was exploratory. What this post investingates is the simple, principles that haven't been obscured by layers of error trapping, abstract code and the like. I like your OMG reaction - I hope you weren't debating it as your read the post :-)

    Bruno
  • I'm a complete newbie at C#/WPF/windows_programming_in_general so I may be completely wrong, but...isn't the .Split(',') call supposed to feature a tab (a '\t' I guess) instead of a comma for the TabTextFileReader class?

    If one searches this page for .Split he/she will encounter it 4 times:

    - 2 times for CommaTextFileReader class

    - 2 times for TabTextFileReader class

    All 4 calls have a comma as parameter. I was expecting a comma in the first 2 instances and a tab in the other 2

Page 1 of 1 (6 items)
Leave a Comment
  • Please add 5 and 8 and type the answer here:
  • Post