Dependency Injection and Circular References

I’ve been using dependency injection (DI) in my code for over a year now. I wasn’t a huge fan of it when I started. The only benefit I have seen from using it is with unit testing. When code is unit tested, its easier to “hide” parts of code you don’t want to test.

I’ve also read about the performance hit for using DI. All code has a cost and I wanted to be preemptive about getting the most out of our code. The company I work for uses Ninject for their DI tool. Many performance benchmarks place Ninject near the bottom of the list for performance. DI’s performance hit is mostly during startup, but that is one of the places I feel we need to be faster.

We didn’t do anything complex with Ninject/DI. Just basic constructor injection. This is the act of using the DI container to populate the IWeapon object when it creates the object based on a Ninject config file.

--Code from Ninject's site.
class Samurai
{
    readonly IWeapon weapon;
    public Samurai(IWeapon weapon) 
    {
        this.weapon = weapon;
    }
    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }
}

class Sword : IWeapon
{
    public void Hit(string target) 
    {
        Console.WriteLine("Chopped {0} clean in half", target);
    }
}

When we use the code, we can just call the following.

Samurai sam = new Samuari();

If you’re new to DI, you’ll assume that this code will break, but Ninject/DI will use its config file to add whatever class you have bound to IWeapon.

This means that removing Ninject and its config file will break the code as the IWeapon object isn’t defined. Using our this is where I was updating our code to manually bind on the default constructor. The updated code would look like the following

--Code from Ninject's site.
class Samurai
{
    readonly IWeapon weapon;
    
    --Default Constructor replaces Ninject.
    public Samurai()
    {
        this.weapon = new Sword();
    }

    public Samurai(IWeapon weapon) 
    {
        this.weapon = weapon;
    }
    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }
}

class Sword : IWeapon
{
    public void Hit(string target) 
    {
        Console.WriteLine("Chopped {0} clean in half", target);
    }
}

If you’re still reading, thanks 🙂 Ninject purists will state that I’m loosing the advantages Ninject provides. If an implementation changes, I can’t make a fix in 1 Ninject config file, but must update code in several places. I’m okay with this performance tradeoff. My implementations don’t change that much and I have find/replace tools that I am comfortable with. Using this method, I still get the advantages of basic DI for unit testing that I wanted in the first place.

Here is where the problem comes in and the title of the article. By using a DI tool such as Ninject, I can easily create circular dependencies in my projects. It turns out that in the project I was attempting to remove Ninject from, two assemblies were each referencing each other. Because we are using Ninject, there is not direct reference in the project references for the other project. Its all brought in via runtime with the DI tool.

At this time, my plans to remove Ninject are on hold as it would take a significant refactoring of the code to remove the circular reference. I am by no way saying not to use DI, but to be careful as it makes it easier to do some things that you don’t want to do.

Hogan Haake