Functions in dependency injection

In this blog post I will show you some results of my experiments with “functional dependency injection” in C# environment.

But first, let’s summarize what is dependency injection and how is used in C# programs.

Dependency inversion principle is about a special form of decoupling software modules(by modules I mean functions, classes, libraries). When following this principle, the conventional dependency relationships are established from high-level, policy-setting modules to low-level.

Put it simply: Dependencies of classes are managed not by classes or functions itself but by some kind of mechanism which is responsible to handle this dependency by a transparent way. Common and right way to do it, is introduce a Dependency Injection Container into your code.

Dependency Injection Container(DI) is usually set of infrastructure classes which manage instances of your service classes.

I don’t mean to provide here detailed description of DI because there are many good resources on this topic e.g. An Absolute Beginner’s Tutorial on Dependency Inversion Principle. If you’re not familiar with this concept please read this article first and then come back here — I’ll wait for you:)

So lets show how classic DI pattern is implemented in C# program. In my examples I will use Windsor Castle container. But you can use your favourite container or language.(This article doesn’t refer only to C# but it is perfect valid for almost every mainstream object oriented language).

First we have to create interface IStorage and class FileStorage which implements IStorage. Responsibility of this class is provide functionality to write some text to a log file.

interface IStorage
{
void Persist(string text);
}
internal class FileStorage : IStorage
{
public void Persist(string text)
{
File.AppendAllText(“log.txt”, text);
}
}

And then we have to create interface ILogger and class Logger which implement this interface.

interface ILogger
{
void LogError(string text);
void LogInfo(string text);
}
class Logger : ILogger
{
private readonly IStorage _storage;
public Logger(IStorage storage)
{
_storage = storage;
}
public void LogError(string text)
{
_storage.Persist(“Error:” +Environment.NewLine + text);
}
public void LogInfo(string text)
{
_storage.Persist(“Info:” + Environment.NewLine + text);
}
}

I think this code is self-explanatory. But to be sure — Class Logger writes text to log and does some additional formatting.

Now we need to register these classes to DI container:

var container = new WindsorContainer();
container.Register(Component.For(typeof(IStorage))
.ImplementedBy(typeof(FileStorage)).LifestyleTransient());
container.Register(Component.For(typeof(ILogger))
.ImplementedBy(typeof(Logger)).LifestyleTransient());

And finally we can use this code to do some logging:

var logger = container.Resolve<ILogger>();
logger.LogError(“Something is wrong”);

So this is a classic way how to manage life of our dependencies and our instances of services classes.

This way of handling dependencies is battle-tested and very useful. So has this dependency handling any disadvantages?

Yes, of course. For some dependencies it can be needless complex. Lets refactor this code to more functional way:

First of all we get rid of interface IStorage and class Storage. We don’t need this:

So our code will look like this:

interface ILogger
{
void LogError(string text);
void LogInfo(string text);
}
class Logger : ILogger
{
private readonly Action<string> _persistAction;
public Logger(Action<string> persistAction)
{
_persistAction = persistAction;
}
public void LogError(string text)
{
_persistAction(“Error:” +Environment.NewLine + text);
}
public void LogInfo(string text)
{
_persistAction(“Info:” + Environment.NewLine + text);
}
}

And registering dependencies:

_container.Register(Component.For(typeof(ILogger))
.UsingFactoryMethod(() =>
new Logger(text => File.AppendAllText(“log.txt”, text)))
.ImplementedBy(typeof(Logger)).LifestyleTransient());

We have removed interface and class for Storage and have replaced them by delegate Action<string>

For this operation we use feature called High Order function — it means that methods can take one or more functions as arguments(second rule is that function/method can returns functions — but in this case this feature/rule isn’t used).

Disadvantages of this approach.

There is one main disadvantage. This approach can lead to more complicated code when you have a large dependency structure.

For example we can modify our code like this:

First lets introduce a new interface:

public interface IFileProvider
{
string GetFileName();
}
class FileProvider .... any implementation of this interface

This interface is responsible for providing file name to which you can log messages. Now we will change code like this:

interface IStorage
{
void Persist(string text);
}
internal class FileStorage : IStorage
{
private readonly IFileProvider _fileProvider;
public void FileStorage(IFileProvider fileProvider)
{
_fileProvider = fileProvider;
}
}
public void Persist(string text)
{
File.AppendAllText(_fileProvider.GetFileName(), text);
}
}

var container = new WindsorContainer();
container.Register(Component.For(typeof(IStorage))
.ImplementedBy(typeof(FileStorage)).LifestyleTransient());
container.Register(Component.For(typeof(ILogger))
.ImplementedBy(typeof(Logger)).LifestyleTransient());
container.Register(Component.For(typeof(IFileProvider))
.ImplementedBy(typeof(FileProvider)).LifestyleTransient());

To keep our functional approach we had to change code registration of dependency like this:

_container.Register(Component.For(typeof(ILogger))
.UsingFactoryMethod(() => new Logger(text =>
{
var fileName = FileProviderFunctions.FileProviderFunc();
File.AppendAllText(fileName, text);
})).ImplementedBy(typeof(Logger)).LifestyleTransient());
//and there we can set our FileProviderFunc
FileProviderFunctions.FileProviderFunc = () => “log.txt”;

Uhh what static classes and static functions? Yes. In this case it fits perfect. If we use static delegate(in this case Func<string> FileProviderFunc) we get polymorphism without unnecessary classes.

So how it works

Factory methods has changed from plain call constructor with lambda expression:

new Logger(text => File.AppendAllText(“log.txt”, text)

To more complicated lambda statement:

new Logger(text =>
{
var fileName = FileProviderFunctions.FileProviderFunc();
File.AppendAllText(fileName, text);
}

In this lambda statement first we have to get file name by function FileProviderFunctions.FileProviderFunc() and after that we can write text to file.

But we can use also our good old class approach.

If we want this method to use IFileProvider. We can use this

_container.Register(Component.For(typeof(ILogger))

.UsingFactoryMethod(kernel => new Logger(text =>
{
var fileProvider = kernel.Resolve<IFileProvider>().Create()
var fileName = fileProvider.GetFileName();
File.AppendAllText(fileName, text);
})).ImplementedBy(typeof(Logger)).LifestyleTransient());

As I said, for someone this can be a little confusing — there can be lot of anonymous functions and for untrained eyes it can seem less readable than classic classes. But I believe it is a matter of personal preference and experience.

Also we have possibility to mix this approaches. For small chunk of functionalities we can use this functional approach and for larger or complex functionalities we can fall back to classic class approach.

So don’t be afraid of static classes and give a try this functional way managing dependencies.