Higher Order Functions in C#: A Practical Example

Written by samwalpole | Published 2021/07/03
Tech Story Tags: csharp | functional-programming | higher-order-functions | programming | repository-pattern | tutorial | dotnet | dotnet-core | web-monetization

TLDR The repository pattern is a 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. 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. For example, the Map function takes a list of a particular data type and a function that returns a new list with the function applied to each of the elements.via the TL;DR App

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. It returns a new list with the function applied to each of the elements in the list (basically equivalent to the 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 };

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

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 using higher-order functions, one would have to define a new Map function for every 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...
}

As you can see in this typical example, there are numerous definitions that all return IEnumerable<Product>. 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;
    }
}
// client code
var allProducts = _productRepository.Get();
var productsByCategoryId = _productRepository.Get(p => p.CategoryId == 1);
var activeProducts = _productRepository.Get(p => p.Active);

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.


Also published on https://samwalpole.com/a-practical-guide-to-higher-order-functions-in-c.


Written by samwalpole | Fullstack .NET and JavaScript web developer. Coding teacher and advocate
Published by HackerNoon on 2021/07/03