19

Context: I am using C#

I designed a class, and in order to isolate it, and make unit testing easier, I am passing in all its dependencies; it does no object instantiation internally. However, instead of referencing interfaces to get the data it needs, I have it referencing general purpose Funcs returning the data/behavior it requires. When I inject its dependencies, I can just do so with lambda expressions.

To me, this seems like a better approach because I do not have to do any tedious mocking during unit testing. Also, if the surrounding implementation has fundamental changes, I will only need change the factory class; no changes in the class containing the logic will be needed.

However, I have never seen IoC done this way before, which makes me think there are some potential pitfalls I may be missing. The only one I can think of is minor incompatibility with earlier version of C# which don't define Func, and this is not an issue in my case.

Are there any problems with using generic delegates/higher order functions such as Func for IoC, as opposed to more specific interfaces?

3
  • 2
    What you're describing are higher-order functions, an important facet of functional programming. Apr 3, 2017 at 19:03
  • 2
    I'd use a delegate instead of a Func since you can name the parameters, where you can state their intent. Apr 4, 2017 at 5:43
  • 2
    related: stackoverflow: ioc-factory-pros-and-contras-for-interface-versus-delegates. @TheCatWhisperer question is more general while the stackoverflow question narrows down to the special case "factory"
    – k3b
    Apr 4, 2017 at 9:34

5 Answers 5

15

If an interface does contain only one function, not more, and there is no compelling reason to introduce two names (the interface name and the function name inside the interface), using a Func instead can avoid unnecessary boilerplate code and is in most cases preferable - just like when you start to design a DTO and recognize it needs only one member attribute.

I guess lots of people are more used to using interfaces, because at the time when dependency injection and IoC were getting popular, there was no real equivalent to the Func class in Java or C++ (I am not even sure if Func was available at that time in C#). So lots of tutorials, examples or textbooks still prefer the interface form, even if using Func would be more elegant.

You might look into my former answer about the interface segregation principle and Ralf Westphal's Flow Design approach. This paradigma implements DI with Func parameters exclusively, for exactly the same reason you mentioned already by yourself (and some more). So as you see, your idea is not a really new one, quite the opposite.

And yes, I used this approach by myself, for production code, for programs which needed to process data in form of a pipeline with several intermediate steps, including unit tests for each of the steps. So from that I can give you first hand experience that it can work very well.

9
  • This program was not even designed with the interface segregation principle in mind, they are basically just used as abstract classes. This is further reason why I went with this approach, the interfaces are not well thought out and mostly useless as only one class implements them anyway. The interface segregation principle does not have to be taken to the extreme, especially now that we have Func, but in my mind, it is still very important. Apr 3, 2017 at 21:57
  • 1
    This program was not even designed with the interface segregation principle in mind - well, according to your description, it was, you were probably just not aware that the term could be applied to your kind of design.
    – Doc Brown
    Apr 4, 2017 at 6:25
  • Doc, I was referring to the code made by my predecessors, which I am refactoring, and of which my new class is somewhat dependent on. Part of the reason I went with this approach is because I plan on making major changes to other parts of the program in the future, including dependencies of this class Apr 4, 2017 at 13:26
  • 1
    "...At the time when dependency injection and IoC were getting popular, there was no real equivalent to the Func class" Doc that's exactly what i almost answered but didn't really feel confident enough! Thanks for verifying my suspicion on that one.
    – GHP
    Apr 4, 2017 at 13:35
  • What about mocking though? Isn't it difficult to mock the funcs? One reason that interfaces are good practice is that they can be easily mocked with mock frameworks. Apr 11, 2020 at 5:45
9

One of the main advantages I find with IoC is that it will allow me to name all my dependencies via naming their interface, and the container will know which one to supply to the constructor by matching the type names. This is convenient and it allows much more descriptive names of dependencies than Func<string, string>.

I also often find that even with a single, simple dependency, sometimes it needs to have more than one function - an interface allows you to group these functions together in a way that is self-documenting, vs having multiple parameters that all read like Func<string, string>, Func<string, int>.

There are definitely times when it's useful to simply have a delegate that is passed in as a dependency. It's a judgement call as to when you use a delegate vs having an interface with very few members. Unless it's really clear what the purpose of the argument is, I will usually err on the side of producing self-documenting code; ie. writing an interface.

3
  • I had to debug someone's code, when the original implementer no longer was there, and I can tell you from first experience, that finding out which lambda is ran where down the stack is a lot of work and a really frustrating process. Had the original implementer used interfaces, it would have been a breeze to find the bug I was looking for.
    – cwap
    Apr 5, 2017 at 6:10
  • cwap, I feel your pain. However, in my particular implementation, the lambdas are all one liners pointing to a single function. They are also all generated in a single place. Apr 5, 2017 at 14:05
  • In my experience, this is just a matter of tooling. ReSharpers Inspect->Incoming Calls does a good job in folling the path up the funcs/lambdas.
    – Christian
    Feb 25, 2020 at 8:34
4

Are there any problems with using generic delegates/higher order functions such as Func for IoC, as opposed to more specific interfaces?

Not really. A Func is its own sort of interface (in the English meaning, not C# meaning). "This parameter is something that supplies X when asked." The Func even has the benefit of lazily supplying the information only as needed. I do this a bit and recommend it in moderation.

As for downsides:

  • IoC containers often do some magic to wire up dependencies in a cascaded sort of way, and probably won't play nice when some things are T and some things are Func<T>.
  • Funcs have some indirection, so can be a little harder to reason about and debug.
  • Funcs delay instantiation, meaning runtime errors may show up at weird times or not at all during testing. It can also increase the chances of order of operations issues and depending on your use, deadlocks in initialization ordering.
  • What you pass into the Func is likely to be a closure, with the slight overhead and complications those entail.
  • Calling a Func is a bit slower than accessing the object directly. (Not enough that you'll notice in any non-trivial program, but it's there)
1
  • 1
    I use the IoC Container to create the factory in a traditional way, then the factory packages the interfaces methods into lambdas, getting around the issue you mentioned in your first point. Good points. Apr 3, 2017 at 21:54
1

Let's take a simple example-- perhaps you are injecting a means of logging.

Injecting a class

class Worker: IWorker
{
    ILogger _logger;

    Worker(ILogger logger)
    {
        _logger = logger;
    }
    void SomeMethod()
    {
        _logger.Debug("This is a debug log statement.");
    }
}        

I think that is pretty clear what is going on. What's more, if you are using an IoC container, you don't even need to inject anything explicitly, you just add to your composition root:

container.RegisterType<ILogger, ConcreteLogger>();
container.RegisterType<IWorker, Worker>();
....
var worker = container.Resolve<IWorker>();

When debugging Worker, a developer just needs to consult the composition root to determine what concrete class is being used.

If a developer needs more complicated logic, he has the whole interface to work with:

    void SomeMethod()
    { 
       if (_logger.IsDebugEnabled) {
           _logger.Debug("This is a debug log statement.");
       }
    }

Injecting a method

class Worker
{
    Action<string> _methodThatLogs;

    Worker(Action<string> methodThatLogs)
    {
        _methodThatLogs = methodThatLogs;
    }
    void SomeMethod()
    {
        _methodThatLogs("This is a logging statement");
    }
}        

First, notice that the constructor parameter has a longer name now, methodThatLogs. This is necessary because you can't tell what an Action<string> is supposed to do. With the interface, it was completely clear, but here we have to resort to relying on parameter naming. This seems inherently less reliable and harder to enforce during a build.

Now, how do we inject this method? Well, the IoC container won't do it for you. So you are left injecting it explicitly when you instantiate Worker. This raises a couple problems:

  1. It is more work to instantiate a Worker
  2. Developers attempting to debug Worker will find it is more difficult to figure out what concrete instance gets called. They can't just consult the composition root; they will have to trace through code.

How about if we need more complicated logic? Your technique only exposes one method. Now I suppose you could bake the complicated stuff into the lambda:

var worker = new Worker((s) => { if (log.IsDebugEnabled) log.Debug(s) } );

but when you are writing your unit tests, how do you test that lambda expression? It's anonymous, so your unit test framework can't instantiate it directly. Maybe you can figure out some clever way to do it, but it'll probably be a bigger PITA than using an interface.

Summary of the differences:

  1. Injecting only a method makes it harder to infer the purpose, whereas an interface clearly communicates the purpose.
  2. Injecting only a method exposes less functionality to the class receiving the injection. Even if you don't need it today, you may need it tomorrow.
  3. You cannot automatically inject only a method using an IoC container.
  4. You cannot tell from the composition root which concrete class is at work in a particular instance.
  5. It is a problem to unit test the lambda expression itself.

If you are OK with all of the above, then it's OK to inject just the method. Otherwise I'd suggest you stick with tradition and inject an interface.

5
  • I should have specified, the class I am making is a process logic class. It is relying on external classes to get the data it needs to make an informed decision, a side effect inducing class such a logger is not used. An issue with your example is that it is poor OO, especially within the context of using an IoC container. Instead of the using an if statement, which adds additional complexity, to the class, it should simply be passed an inert logger. Also, introducing logic in the lambdas would indeed make testing harder... defeating the purpose of its use, which is why it is not done. Apr 4, 2017 at 13:53
  • The lambdas point to an interface method in the application, and construct arbitrary data structures in unit tests. Apr 4, 2017 at 14:04
  • Why do people always focus on the minutiae of what is obviously an arbitrary example? Well if you insist on talking about proper logging, an inert logger would be a terrible idea in some cases, e.g. when the string to be logged is expensive to compute.
    – John Wu
    Apr 4, 2017 at 15:10
  • I am not sure what you mean by "lambdas point to an Interface method." I think you'd have to inject an implementation of a method that matches the delegate signature. If the method happens to belong to an interface, that is incidental; there is no compile- or run-time check to ensure that it does. Am I misunderstanding? Perhaps you could include a code example in your post?
    – John Wu
    Apr 4, 2017 at 17:57
  • 1
    I can't say I agree with your conclusions here. For one thing, you should be coupling your class to the minimal amount of dependencies, so using lambdas ensures that each injected item is exactly one dependency and not a conflation of multiple things in an interface. You can also support a pattern of building up a viable Worker one dependency at a time, allowing each lambda to be injected independently until all dependencies are satisfied. This is similar to partial-application in FP. Furthermore, using lambdas helps eliminate implicit state and hidden coupling between dependencies. Dec 31, 2019 at 14:07
0

Consider the following code I wrote long ago:

public interface IPhysicalPathMapper
{
    /// <summary>
    /// Gets the physical path represented by the relative URL.
    /// </summary>
    /// <param name="relativeURL"></param>
    /// <returns></returns>
    String GetPhysicalPath(String relativeURL);
}

public class EmailBuilder : IEmailBuilder
{
    public IPhysicalPathMapper PhysicalPathMapper { get; set; }
    public ITextFileLoader TextFileLoader { get; set; }
    public IEmailTemplateParser EmailTemplateParser { get; set; }
    public IEmaiBodyRenderer EmailBodyRenderer { get; set; }

    public String FromAddress { get; set; }

    public MailMessage BuildMailMessage(String templateRelativeURL, Object model, IEnumerable<String> toAddresses)
    {
        String templateText = this.TextFileLoader.LoadTextFromFile(this.PhysicalPathMapper.GetPhysicalPath(templateRelativeURL));

        EmailTemplate template = this.EmailTemplateParser.Parse(templateText);

        MailMessage email = new MailMessage()
        {
            From = new MailAddress(this.FromAddress),
            Subject = template.Subject,
            IsBodyHtml = true,
            Body = this.EmailBodyRenderer.RenderBodyToHtml(template.BodyTemplate, model)
        };

        foreach (MailAddress recipient in toAddresses.Select<String, MailAddress>(toAddress => new MailAddress(toAddress)))
        {
            email.To.Add(recipient);
        }

        return email;
    }
}

It takes a relative location to a template file, loads it into memory, renders the message body, and assembles the e-mail object.

You might look at IPhysicalPathMapper and think, "There's only one function. That could be a Func." But actually, the problem here is that IPhysicalPathMapper shouldn't even exist. A much better solution is to just parameterize the path:

public class EmailBuilder : IEmailBuilder
{
    public ITextFileLoader TextFileLoader { get; set; }
    public IEmailTemplateParser EmailTemplateParser { get; set; }
    public IEmaiBodyRenderer EmailBodyRenderer { get; set; }

    public String FromAddress { get; set; }

    public MailMessage BuildMailMessage(String templatePath, Object model, IEnumerable<String> toAddresses)
    {
        String templateText = this.TextFileLoader.LoadTextFromFile(templatePath);

        EmailTemplate template = this.EmailTemplateParser.Parse(templateText);

        MailMessage email = new MailMessage()
        {
            From = new MailAddress(this.FromAddress),
            Subject = template.Subject,
            IsBodyHtml = true,
            Body = this.EmailBodyRenderer.RenderBodyToHtml(template.BodyTemplate, model)
        };

        foreach (MailAddress recipient in toAddresses.Select<String, MailAddress>(toAddress => new MailAddress(toAddress)))
        {
            email.To.Add(recipient);
        }

        return email;
    }
}

This raises a whole host of other questions to improve this code. E.g., maybe it should just accept an EmailTemplate, and then maybe it should accept a pre-rendered template, and then maybe it should just be in-lined.

This is why I don't like inversion of control as a pervasive pattern. It's generally held up as this god-like solution to composing all your code. But in reality, if you use it pervasively (as opposed to sparingly), it makes your code much worse by encouraging you to introduce a lot of unnecessary interfaces that are used completely backwards. (Backwards in the sense that the caller should really be responsible for evaluating those dependencies and passing the result in rather than the class itself invoking the call.)

Interfaces should be used sparingly, and inversion of control and dependency injection should also be used sparingly. If you have larges amounts of them, your code becomes much more difficult to decipher.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.