How To Apply Functional Programming In C#

How To Apply Functional Programming In C#

3 min read ·

Thank you to our sponsors who keep this newsletter free to the reader:

Today's issue is sponsored by Localizely, the translation management platform that helps you translate texts in your app for targeting multilingual markets.

And by Hasura, an open-source engine that gives instant GraphQL and REST APIs on new or existing SQL Server, enabling teams to ship apps faster.

Although C# is an object-oriented programming language, it received many new functional features in recent versions.

To mention just a few of these features:

  • Pattern matching
  • Switch expressions
  • Records

You're probably already doing functional programming without even knowing it.

Do you use LINQ? If you do, then you're doing functional programming. Because LINQ is a functional .NET library.

In today's issue, I will show you how to refactor some imperative code with functional programming.

Let's dive in.

Benefits Of Functional Programming

Before we take a look at some code, let's see what are the benefits of using functional programming:

  • Emphasis on immutability
  • Emphasis on function purity
  • Code is easier to reason about
  • Less prone to bugs and errors
  • Ability to compose functions and create higher-order functions
  • Easier to test and debug

From my experience, I think functional programming is more enjoyable once you get used to it. Starting out, it feels strange because your old object-oriented programming habits will kick in. But after a while, functional programming feels easier to work with than imperative code.

Starting With Imperative Code

Imperative programming is the most basic programming approach. We describe a step-by-step process to execute a program. It's easier for beginners to reason with imperative code by following along with the steps in the process.

Here's an example of an EmailValidator class written with imperative code:

public class EmailValidator
{
    private const int MaxLength = 255;

    public (bool IsValid, string? Error) Validate(string email)
    {
        if (string.IsNullOrEmpty(email))
        {
            return (false, "Email is empty");
        }

        if (email.Length > MaxLength)
        {
            return (false, "Email is too long");
        }

        if (email.Split('@').Length != 2)
        {
            return (false, "Email format is invalid");
        }

        if (Uri.CheckHostName(email.Split('@')[1]) == UriHostNameType.Unknown)
        {
            return (false, "Email domain is invalid");
        }

        return (true, null);
    }
}

You can clearly see the distinct steps:

  • Check if email is null or empty
  • Check if email is not too long
  • Check if email format is valid
  • Check if email domain is valid

Let's see how we can refactor this using functional programming.

Applying Functional Programming

The basic building block in functional programming is - a function. And programs are written by composing function calls. There are a few other things you need to keep in mind, like keeping your functions pure. A function is pure if it always returns the same output for the same input.

We can capture each step from the imperative version of EmailValidator with a Func delegate. To also capture the respective error message together with the validation check, we can use a tuple. And since we know all of our validation steps, we can create an array of Func delegates to store all of the individual functions.

public class EmailValidator
{
    const int MaxLength = 255;

    static readonly Func<string, (bool IsValid, string? Error)>[] _validations =
    {
        email => (!string.IsNullOrEmpty(email), "Email is empty"),
        email => (email.Length <= MaxLength, "Email is too long"),
        email => (email.Split('@').Length == 2, "Email format is invalid"),
        email => (
            Uri.CheckHostName(email.Split('@')[1]) != UriHostNameType.Unknown,
            "Email domain is invalid")
    };

    static readonly (bool IsValid, string? Error) _successResult = (true, null);

    public (bool IsValid, string? Error) Validate(string email)
    {
        var validationResult = _validations
            .Select(func => func(email))
            .FirstOrDefault(func => !func.IsValid);

        return validationResult is { IsValid: false, Error: { Length: >0 } } ?
            validationResult : _successResult;
    }
}

Notice that this allows us to do all sorts of interesting things with the _validations array. How hard would it be to modify this function to return all of the errors instead of just the first one?

If you're thinking we can use LINQ's Select method somehow, you're thinking in the right direction.

Further Reading

We only scratched the surface of what functional programming is, and what you can do with it. If you want to learn more, here are some learning materials:

Thank you for reading, and have a wonderful Saturday.


Whenever you're ready, there are 4 ways I can help you:

  1. (COMING SOON) REST APIs in ASP.NET Core: You will learn how to build production-ready REST APIs using the latest ASP.NET Core features and best practices. It includes a fully functional UI application that we'll integrate with the REST API. Join the waitlist!
  2. Pragmatic Clean Architecture: Join 3,150+ students in this comprehensive course that will teach you the system I use to ship production-ready applications using Clean Architecture. Learn how to apply the best practices of modern software architecture.
  3. Modular Monolith Architecture: Join 1,050+ engineers in this in-depth course that will transform the way you build modern systems. You will learn the best practices for applying the Modular Monolith architecture in a real-world scenario.
  4. Patreon Community: Join a community of 1,000+ engineers and software architects. You will also unlock access to the source code I use in my YouTube videos, early access to future videos, and exclusive discounts for my courses.
  5. Promote yourself to 58,000+ subscribers by sponsoring this newsletter.

Become a Better .NET Software Engineer

Join 58,000+ engineers who are improving their skills every Saturday morning.