DEV Community

Cover image for A Practical Guide to Higher Order Functions in C#
Sam Walpole
Sam Walpole

Posted on • Originally published at samwalpole.com

A Practical Guide to Higher Order Functions in C#

If you have been programming for any length of time, you may well have come across higher order functions, but (like me) may not have fully appreciated just how powerful they can be. If you've not heard of them before, never fear, I have an explanation below.

What are higher order functions?

Simply put, a higher order function is a function whose input or output is also a function.

In the following example, the Map function takes a list of a particular data type and a function, and returns a new list with the function applied to each of the elements in the list (basically equivalent to LINQs Select method).

// definition
List<TOut> Map<TIn, TOut>(List<TIn> list, Func<TIn, TOut> mapper)
{
    var newList = new List<TOut>();
    foreach (var item in list)
    {
        var newItem = mapper(item);
        newList.Add(newItem);
    }

    return newList;
}

// usage
var myList = new List<int> { 1, 2, 3, 4, 5 };

int multiplyBy2 (int num) => num * 2;
var multipliedList = Map(myList, multiplyBy2);
// output { 2, 4, 6, 8, 10 };

bool isEven (int num) => num % 2 == 0;
var isEvenList = Map(myList, isEven);
// output { false, true, false, true, false };
Enter fullscreen mode Exit fullscreen mode

Here is an example of a higher order function that returns a function. First you call the Add function with a given integer, which returns a function that adds that first integer to any given other integer.

Func<int, int> Add(int a) => (int b) => a + b;

var add9 = Add(9);

var sum1 = add9(1);
// output 10

var sum2 = add9(2);
// output 11
Enter fullscreen mode Exit fullscreen mode

Hopefully by now you can see why higher order functions are so useful; they allow complex code to be reused in a highly flexible way by letting the client define their own input/output functions. For example, without the use of higher order functions, one would have to define a new Map function for every single type of mapping they needed.

Practical Example - Repository Pattern

The repository pattern is a very common design pattern used as a data access abstraction. It allows you to perform your typical CRUD operations without the client having to interact directly with the data provider. A typical repository interface may look something like this:

public interface IProductRepository
{
    int Create(Product product);
    bool Update(Product product);
    bool Delete(int id);
    Product GetById(int id);
    IEnumerable<Product> GetAll();
    IEnumerable<Product> GetByCategoryId(int categoryId);
    IEnumerable<Product> GetActive();
    // etc...
}
Enter fullscreen mode Exit fullscreen mode

As you can see in this typical example, there are numerous definitions that all return IEnumerable<Product>, and every time you need another specific type of product filter, that requires adding to the interface and writing an entirely new implementation. That's not great for maintainability...

Instead, using higher order functions, we can define a single method that gets IEnumerable<Product> that takes a filter function as an input. That way the client is free to define their own filters and the ProductRepository doesn't need to keep being updated with new implementations.

interface IProductRepository
{
    // create, update, delete omitted

    IEnumerable<Product> Get(Func<Product, bool> filter = null);
}

public class ProductRepository : IProductRepository
{
    private readonly List<Product> _products = new List<Product>(); // data source

    public IEnumerable<Product> Get(Func<Product, bool> filter = null)
    {
        // typically you might use the LINQ Where method here
        // but using the foreach to be clear what is happening

        if (filter is null) return _products;     

        var filteredList = new List<Product>();
        foreach(var product in _products)
        {
            if(filter(product))
            {
                filteredList.Add(product);
            }
        }

        return filteredList;
    }
}
Enter fullscreen mode Exit fullscreen mode
// client code
var allProducts = _productRepository.Get();
var productsByCategoryId = _productRepository.Get(p => p.CategoryId == 1);
var activeProducts = _productRepository.Get(p => p.Active);
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this article I have introduced the concepts of higher order functions and demonstrated how they are extremely useful in creating reusable and flexible code that is easy to maintain and understand. I have also given a practical use case that you may find in a .NET enterprise application - making the repository pattern more reusable.

I post mostly about full stack .NET and Vue web development. To make sure that you don't miss out on any posts, please follow this blog and subscribe to my newsletter. If you found this post helpful, please like it and share it. You can also find me on Twitter.

Top comments (0)