Functional C# Application Composition, Part 2: Delegates

In my previous post on the Stateless Single-Responsibility Object (SSRO) approach to C# application composition I reviewed the concept and its shortcomings.

To recap:

  1. You end up with a ton of classes splitting up semi-related logic across multiple files. This is necessary to conform to the classname == filename convention.
  2. In order to mock, you must generally use either the virtual keyword or interfaces, even though you have no actual intention of providing multiple implementations except for in tests.
  3. You can’t properly enforce the “stateless” nature of the SSRO. Instead all programmers who alter the code must know and follow the convention.

How to solve the problem? Functions!

What I really want is a function, specifically a function-as-first-class-citizen. Ideally, what I’d like to do is declare a bunch of stateless, independent functions and hide my dependencies with partial application on those functions. Tests can then provide mock functions for dependencies.

How can I accomplish this in C#? Delegates.

A delegate in C# is essentially a function signature, but it is also a type that can be used as an argument to functions. Additionally, you can mock delegates and register them for dependency injection. Thanks to method groups and lambdas, it is easy to coerce existing methods into delegate types. And as I’ll detail later in this post, I’ve created a NuGet package to streamline the process of registering delegates as dependencies.

An Example

Let’s revisit our display-a-hypotenuse example from the last post, this time using delegates instead of objects:

// this is the delegate outside consumers will use
public string delegate DisplayHypotenuse(double x, double y);

public static class Pythagoras
{
    // delegates inside the class are used internally but public so tests can talk about them
    
    public delegate double Add(double x, double y);
    public static double AddImpl(double x, double y)
    {
        return x + y;
    }

    public delegate double Mult(double x, double y);
    public static double MultImpl(double x, double y)
    {
        return x * y;
    }

    public delegate double Sqrt(double x);
    public static double SqrtImpl(double x)
    {
        return System.Math.Sqrt(x);
    }

    public static double CalculateHypotenuseFull(Add add, Mult mult, Sqrt sqrt, double x, double y)
    {
        return sqrt(add(mult(x,x), mult(y,y)));
    }

    public delegate double CalculateHypotenuse(double x, double y);
    // don't test this function directly except through integration or system tests, conforms to delegate
    public static double CalculateHypotenuseImpl(double x, double y)
    {
        return CalculateHypotenuseFull(AddImpl, MultImpl, SqrtImpl, x, y);
    }

    public delegate string DoubleToString(double value);
    public static string DoubleToStringImpl(double value)
    {
        return value.ToString();
    }

    public static string DisplayHypotenuseFull(CalculateHypotenuse calcH, DoubleToString dToS, double x, double y)
    {
        return dToS(calcH(x, y));
    }

    // conforms to DisplayHypotenuse delegate, don't unit test this function
    public static string DisplayHypotenuseImpl(double x, double y)
    {
        return DisplayHypotenuseFull(CalculateHypotenuseImpl, DisplayToStringImpl, x, y);
    }
}

That’s better. Much of the boilerplate is gone now, and the code is about a third shorter than my initial implementation.

A Spoonful of Functional Programming Helps the Code Smell Go Down

As we’ve seen, a delegate-driven approach solves my problems with Stateless Single-Responsibility Objects:

  1. The code clearly indicates that these functions are independent, yet keeps them on one file/class.
  2. Dependencies are easy to mock out or replace, without using interfaces or the virtual keyword.
  3. Dependencies are not held as state by a class; moreover the functions being declared as static indicates that they are not meant to hold state.
  4. There’s still a bit more we can do, though, particularly where dependency injection is concerned. The functions CalculateHypotenuseImpl and DisplayHypotenuseImpl basically use partial application to eliminate the extra dependencies and conform to the desired delegate signature. It’s important, but not that interesting. This sort of boilerplate code will only get more prevalent as an application grows in size and complexity. Thankfully, I’ve found a way to eliminate this boilerplate.

    Next Time on Functional C# Application Composition

    In the thrilling conclusion of my blog post series, we’ll take a look at my Nuget package MethodToDelegate and how it can be used to eliminate the boilerplate of providing delegate dependencies. See you then!


    This is the second post in a series on functional C# application composition:

    1. Shortcomings of Single-Responsibility Objects
    2. Delegates
    3. MethodToDelegate