Adding automatic resolution of Func<T> and Lazy<T>

Coordinator
Oct 14, 2013 at 8:22 PM
Edited Oct 14, 2013 at 9:36 PM
This question was asked by blueling on this thread.

Have you considered adding automatic resolution of Func<T> and Lazy<T> Parameters in ctors? Currently with Simple Injector it is necessary to explicitly register the Func<T> / Lazy<T> factory...
Coordinator
Oct 14, 2013 at 9:07 PM
I have considered adding support for resolving Func<T> and Lazy<T> but I came to the conclusion that adding OOTB is actually a bad idea. Although it is useful in some cases, it comes down to the same old argument: it doesn't guide developers to the pit of success.

Application code should not be injected with Func<T> and Lazy<T> types. Func<T> and Lazy<T> are implementation details. They communicate details that a consumer should not have to worry about.

Take an imaginary application where the developers wrote a DbLogger class (implementing ILogger) that takes time to initialize (it does some database initialization when it is created). Since creation takes time, creation of object graphs is delayed, while the logger is just sparsely used. To prevent the performance hit, the developers start injecting Lazy<ILogger> dependencies into constructors.

Although this solved their problem, there are several problems with this approach.
  • First of all, all consumers had to be changed. Instead of injecting an ILogger, they now have to use a Lazy<ILogger> and instead of calling this.logger.Log("") they will now have to call this.logger.Value.Log("").
  • Not only does this clutter the application code, it also makes unit testing (a bit) harder.
  • But more importantly, the Lazy<ILogger> is actually leaking implementation details, since the Lazy<ILogger> abstraction is clearly created with the heavy DbLogger in mind.
  • In fact, every registered component could have performance penalties, so we'd be better starting of with injecting all dependencies as Lazy<T> from the start, to prevent having to make sweeping changes throughout the application later on. But of course this would be insane.
  • Last thing to note is that constructors should be simple, fast and do nothing more than storing dependencies.
So instead of injecting a Lazy<ILogger> what the developers should have been doing in this case is injecting a proxy class into the consumers that allowed delaying the creation of this DbLogger. This proxy might look like this:
public class LazyLoggerProxy : ILogger
{
    private readonly Lazy<ILogger> logger;

    public LazyLoggerProxy(Lazy<ILogger> logger)
    {
        this.logger = logger;
    }

    void ILogger.Log(LogEntry entry)
    {
        this.logger.Value.Log(entry);
    }
}
By registering this LazyLoggerProxy the rest of the application stays oblivious of the fact that the creation of the real logger is delayed. They don't have to know and they shouldn't know.

Again, injecting them can be useful. As you saw, the LazyLoggerProxy depends on Lazy<ILogger>. The LazyLoggerProxy class however is easily registered even without explicit support for Lazy<T>, but when you have many lazy implementations, there's probably something wrong with your design and your constructors are definitely not simple.

The same argument holds for Func<T>. Although almost every application needs factories, having many factories is a smell. Most of the time consumers should have to create instances. Problem with factories is that you break the dependency graph. You delay the creation, making it hard to verify the object graph.

So, because of these arguments, Simple Injector does not has OOTB support for injecting Lazy<T> and Func<T>. Adding this yourself however, is quite easy as can be seen here and here.
Marked as answer by dot_NET_Junkie on 3/2/2014 at 11:12 AM