Introduction to Inversion of Control in C# with Dry IoC

PUBLISHED ON APR 23, 2016 — .NET, DESIGN PATTERNS

Recently I’ve been brushing up on some basic skills, and dependency injection with Inversion of Control is one of those things. Previously when doing this, I’ve stuck to the most commonly used variations of IoC with .NET - Unity and Ninject.

However, after coming across this GitHub example I wanted to give Dry IoC a try given it’s performance benefits over most others.

All this code can be found on my GitHub account here.

The most basic use case of IoC is when you’re resolving an interface to a class implementation. Without IoC, you’d need to know the exact implementation of this interface whenever it’s needed, like so:

IClient client = new TestClient();

Obviously this isn’t ideal as it keeps your code tightly coupled - you don’t want Class A to know about the implementation details of Class B. Avoiding this is where Inversion of Control and Dependency Injection comes in.

When using Dry IoC you can instead register the interface against the implementation when configuring the container:

Container.Register<IClient, TestClient>();

Now you can then use that container to resolve the interface whenever required, like so:

IClient resolvedClient = Container.Resolve<IClient>();

The same principle also applies when you’re registering a singleton, simply passing the Reuse.Singleton parameter when registering will ensure that the same instance is always returned:

Container.Register<ISingletonClient, SingletonClient>(Reuse.Singleton);

I’ve given this interface a method to change a string on the implementation, and a method to print that string out to the console. This allows you to see that changing the string on the first resolved interface is reflected on the second:

ISingletonClient firstSingleton = Container.Resolve<ISingletonClient>();
firstSingleton.Print();
firstSingleton.ChangeString("I've now been modified!");

ISingletonClient secondSingleton = Container.Resolve<ISingletonClient>();
secondSingleton.Print();

Obviously, this is the most basic usage you can find, so let’s start to dig a little deeper.

The next thing that might be needed is when you have an interface that requires a parameter in the constructor. There are two different ways of doing this depending on whether you’re registering against an implementation that requires a primitive type, or a complex object.

For a primitive type you simply need to specify the parameter when registering the interface by using Made.Of to set up the factory method for the constructor:

Container.Register<IServiceWithParameter, ServiceWithParameter>(Made.Of(() => new ServiceWithParameter(Arg.Index<string>(0)), requestIgnored => "this is the parameter"));

While this looks quite complex at first glance, all it’s doing is specifying that when this interface is requested, the constructor should be invoked, and should take the given string as a parameter. This interface then gets resolved as normal when it’s required, and we can print out the parameter to see that this has worked:

IServiceWithParameter resolvedWithPrimitiveParameter = Container.Resolve<IServiceWithParameter>();
resolvedWithPrimitiveParameter.PrintParameter();

Registering with a complex object to be passed in works in much the same way, however it requires that we register an instance of the object with the container:

Container.Register<TestObject>();
var testObj = new TestObject
{
      ObjectName = "Ian",
      ObjectType = "Person"
};
Container.RegisterInstance<TestObject>(testObj, serviceKey: "obj1");

The code above will register an instance of the TestObject class, giving it the key obj1 which can then be used to resolve it. Now that this object is available we can register the interface, specifying that this implementation of the object should be used in the constructor:

Container.Register<IServiceWithTypeParameter, ServiceWithTypeParameter>(made: Parameters.Of.Type<TestObject>(serviceKey: "obj1"));

Again, the sample code on GitHub contains a method that will print out each field so you can see they have been populated. Registering an interface like this could be very useful, for example if you require some configuration settings to be applied that might need to change depending on whether you’re running in Debug or in Production.

This is all well and good, but for the most part when you’re using interfaces you’ll have multiple different implementations. In this case you need to be able to retrieve the specific implementation you want, or perhaps in some cases retrieve all registered implementations of the interface, so let’s take a look at those.

Registering multiple implementations to the container is easy - simply register them as normal, but pass a key when doing so that can later be used to retrieve it. The key is an object type - I’ve used an enum as an easy way to keep track of them:

Container.Register<IMultipleImplementations, MultipleImplementationOne>(serviceKey: InterfaceKey.FirstKey);
Container.Register<IMultipleImplementations, MultipleImplementationTwo>(serviceKey: InterfaceKey.SecondKey);

Now they’ve been registered, they can be resolved easily enough again, just by specifying the key you registered them against:

IMultipleImplementations firstImplementation = Container.Resolve<IMultipleImplementations>(serviceKey: InterfaceKey.FirstKey);
IMultipleImplementations secondImplementation = Container.Resolve<IMultipleImplementations>(serviceKey: InterfaceKey.SecondKey);

If you’re wanting to resolve all implementations of the interface you can do so into an IEnumerable by using ResolveMany instead of Resolve:

IEnumerable<IMultipleImplementations> allImplementations = Container.ResolveMany<IMultipleImplementations>();

Again, the sample code on GitHub includes Print methods on these implementations so that you can see the correct classes are being resolved.

I hope this has given a good basic introduction to Inversion of Control with C# and Dry IoC. In a future post I’ll expand on this and show how you can use IoC and Dependency Injection to help you follow the principles of TDD.

Link to the code on GitHub can be found here.

comments powered by Disqus