I’m reading a lot of blogs about development and .Net. One of the blogs I read is from Jimmy Bogard and one of the recent posts there is about guidelines for using Dependency Injection container.
I got involved in comments and one of the commenters was suggesting using Func<IFoo>
as injection instead of injecting the instance for IFoo
. And reasons for that were:
The “captive dependency” problem… objects with more constrained lifetime being held by objects with longer->lived lifetime.
The (wasteful) big-bang object graph problem… when an MVC action only requires 1 dependency, but the dependency graph for all dependencies within the controller need to be resolved.
The occasional property injection… when its 2am and I don’t feel like fixing a circular dependency issue (don’t hate).
And a follow up comment:
If you have circular dependencies, you aren’t immediately evaluating the circular path
I’d like to address this first, as it bothers me very much myself. It is best recommended to avoid circular dependency and it is entirely possible (I have not yet came across the situation where code could not be refactored out of that). In classical DI you’d fix circular dependency with property injection. Let’s try to fix this problem with injection of a function.
Here is a simple situation with classes:
public interface IBar
{
void DoBar();
}
public class Bar : IBar
{
private readonly Func<IFoo> fooFunc;
public Bar(Func<IFoo> fooFunc)
{
this.fooFunc = fooFunc;
}
public void DoBar()
{
var foo = fooFunc.Invoke();
foo.DoFoo();
Thread.Sleep(1000);
Console.WriteLine("Executing Bar.DoBar");
}
}
And Foo class:
public interface IFoo
{
void DoFoo();
}
public class Foo : IFoo
{
private readonly Func<IBar> barFunc;
public Foo(Func<IBar> barFunc)
{
this.barFunc = barFunc;
}
public void DoFoo()
{
var bar = barFunc.Invoke();
bar.DoBar();
Thread.Sleep(1000);
Console.WriteLine("Executing Foo.DoFoo");
}
}
Foo
depends on IBar
and Bar
depends on IFoo
. Simple circular dependency with proposed injection of functions that resolve actual class entities.
Let’s see how we can create instance of Foo
and instance of Bar
Func<IBar> barFunc = () => new Bar(???);
Func<IFoo> fooFunc = () => new Foo(barFunc);
We still have circular dependency, because we can’t create a function to resolve IBar
without function that resolves IFoo
first.
Ok, so no what? Let’s try to have one of the objects depend on function and another on an interface:
public class Bar : IBar
{
private readonly IFoo foo;
public Bar(IFoo foo)
{
this.foo = foo;
}
public void DoBar()
{
foo.DoFoo();
Thread.Sleep(1000);
Console.WriteLine("Executing Bar.DoBar");
}
}
And Foo
class is unchanged. And try to resolve both of the objects:
Func<IBar> barFunc = () => new Bar(new Foo(???));
Func<IFoo> fooFunc = () => new Foo(barFunc);
We still are in the same situation. We can’t resolve new instance of Bar
because we can’t have Foo
.
Another attempt – let’s do property injection:
public class Bar : IBar
{
public Func<IFoo> FooFunc { get; set; }
public Bar()
{
}
public void DoBar()
{
var foo = FooFunc.Invoke();
foo.DoFoo();
Thread.Sleep(1000);
Console.WriteLine("Executing Bar.DoBar");
}
}
Now we are talking:
Func<IBar> barFunc = () => new Bar();
Func<IFoo> fooFunc = () => new Foo(barFunc);
var foo = fooFunc.Invoke();
var bar = new Bar()
{
FooFunc = fooFunc,
};
bar.DoBar();
The only problem that we still have circular dependency, only run-time, rather than compile time. See if you can figure out why it happens (just try running the code)! I’ve created a gist with full code I’ve tried running. Functions are unwrapped from lambdas, so you can set breakpoints and step into them while debugging.
Now, see if you notice a great big deception I’ve given you here. The dependency is very circular on functional level: DoBar()
calls DoFoo()
calls DoBar()
calls DoFoo()
, etc. It is impossible to run these functions: it is an infinite recursion/loop/whatever. And not type of wiring up will help you out. You’ll need to remove one of the function invocations from the classes:
public void DoFoo()
{
Thread.Sleep(1000);
Console.WriteLine("Executing Foo.DoFoo");
}
Now you can run the sample and it won’t explode with a series of exceptions. But our object wiring looks complex and is not much different from direct injection coupled with property injection of an object:
public class Bar : IBar
{
public IFoo Foo { get; set; }
public Bar()
{
}
public void DoBar()
{
if (Foo == null)
{
throw new Exception("Foo property is not populated");
}
Foo.DoFoo();
Console.WriteLine("Executing Foo.DoFoo");
}
}
public class Foo : IFoo
{
private readonly IBar bar;
public Foo(IBar bar)
{
this.bar = bar;
}
public void DoFoo()
{
Thread.Sleep(1000);
Console.WriteLine("Executing Foo.DoFoo");
}
}
Wiring up and execution looks like this:
var bar = new Bar();
var foo = new Foo(bar);
bar.Foo = foo;
foo.DoFoo();
bar.DoBar();
The second version looks much more simple than the version with functions
Containers and function injection
In the same discussion I said that containers are not geared toward injection of functions. Well, yes, you can totally do this (Autofac):
var builder = new ContainerBuilder();
Func<IBar> barFunc = () => new Bar();
builder.RegisterInstance(barFunc);
builder.RegisterType<Foo>().As<IFoo>();
var container = builder.Build();
var foo = container.Resolve<IFoo>();
foo.DoFoo();
And it will work (given that Foo
constructor takes Func<IBar>
). But with given registration you can’t resolve Bar
because it is not registered – we only registered Func<IBar>
, but not IBar
.
And I’m not aware of the way to register Func<IBar>
together with IBar
in one go, making it double-effort. Also imagine I have hundreds on ICommandHandler<TCommand>
classes and hundreds of IQueryHandler<TQuery, TResult>
. How can you register that with functions? Add a few decorators on top of that.
Probably one can come up with clever methods to do it, but that’ll be an addition to containers, not built-in functionality. And this is what I called “containers are not geared towards”.
Conclusion
However interesting this idea might seem like, I’ll stick to the classical DI with classes depending on interfaces. And all the issues discussed can be avoided by mostly following SOLID principles; no circular dependency; no massive graphs of objects; object resolution should be cheap and simple. To avoid captive dependency use Decoraptors.