Adapting Match in C#: A Functional Programming Pattern

RickRick
4 min read

Origins

Pattern matching, commonly known as Match in functional programming, originates from functional programming languages like ML, Haskell, and F#. The concept is rooted in mathematical pattern matching and algebraic data types (ADTs). In these languages, pattern matching serves as a more powerful and expressive alternative to traditional imperative conditional statements.

The pattern matching concept was first introduced in the ISWIM programming language by Peter J. Landin in 1966 and was later popularized by ML and its descendants. While C# has gradually incorporated more functional programming features, including some pattern matching capabilities since C# 7.0, implementing a full Match operation gives us additional power and expressiveness that's closer to pure functional programming languages.

public class Match<T>
{
    private readonly T _value;

    private Match(T value)
    {
        _value = value;
    }

    public static Match<T> Create(T value) => new Match<T>(value);

    public TResult With<TResult>(
        Func<T, bool> predicate,
        Func<T, TResult> result,
        Func<T, TResult> otherwise)
    {
        return predicate(_value) ? result(_value) : otherwise(_value);
    }

    public TResult With<TResult>(
        Dictionary<Func<T, bool>, Func<T, TResult>> matches,
        Func<T, TResult> otherwise)
    {
        foreach (var match in matches)
        {
            if (match.Key(_value))
            {
                return match.Value(_value);
            }
        }
        return otherwise(_value);
    }
}

This implementation provides two main matching approaches:

  1. A simple predicate-based match with a single condition

  2. A dictionary-based match that can handle multiple conditions

Basic Usage Example

Let's look at how to use this Match implementation in practice:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public class Program
{
    public static void Main()
    {
        var person = new Person { Name = "John", Age = 25 };

        // Simple match
        var result = Match<Person>
            .Create(person)
            .With(
                p => p.Age >= 18,
                p => $"{p.Name} is an adult",
                p => $"{p.Name} is a minor"
            );

        // Multiple conditions match
        var detailedResult = Match<Person>
            .Create(person)
            .With(
                new Dictionary<Func<Person, bool>, Func<Person, string>>
                {
                    { p => p.Age < 13, p => $"{p.Name} is a child" },
                    { p => p.Age < 20, p => $"{p.Name} is a teenager" },
                    { p => p.Age < 30, p => $"{p.Name} is a young adult" }
                },
                p => $"{p.Name} is an adult"
            );
    }
}

This pattern brings several benefits:

  1. Declarative Code: The matching syntax makes the code's intent clearer

  2. Type Safety: The pattern ensures type consistency across all match cases

  3. Exhaustiveness: The otherwise case ensures all possibilities are handled

  4. Composability: Matches can be easily combined and nested

Advanced Usage with Option Type

The Match pattern becomes even more powerful when combined with the Option/Maybe type:

public class Option<T>
{
    private readonly T _value;
    private readonly bool _hasValue;

    private Option(T value, bool hasValue)
    {
        _value = value;
        _hasValue = hasValue;
    }

    public static Option<T> Some(T value) => new Option<T>(value, true);
    public static Option<T> None() => new Option<T>(default, false);

    public TResult Match<TResult>(
        Func<T, TResult> some,
        Func<TResult> none)
    {
        return _hasValue ? some(_value) : none();
    }
}

// Usage example
var optionalPerson = Option<Person>.Some(new Person { Name = "John", Age = 25 });

var greeting = optionalPerson.Match(
    some: person => $"Hello, {person.Name}!",
    none: () => "Hello, guest!"
);

Conclusion

Implementing Match in C# brings several functional programming benefits to your codebase:

  1. Better Error Handling: Combined with Option types, Match provides a robust way to handle null cases and errors

  2. More Readable Code: Pattern matching makes complex conditional logic more readable and maintainable

  3. Type Safety: The pattern enforces type safety at compile time

  4. Functional Style: It enables a more functional programming style in C#, leading to more declarative and less imperative code

While C# continues to evolve and incorporate more functional programming features natively, implementing custom Match operations can provide additional power and flexibility in your code. This pattern is particularly useful when working with domain models, handling complex business logic, or dealing with data transformations.

The key is to use Match where it makes sense - typically in scenarios where you have complex branching logic or when working with Optional types. Like any pattern, it's important to balance its use with readability and team familiarity with functional programming concepts.

Thank you for reading this article. See you next time.

0
Subscribe to my newsletter

Read articles from Rick directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Rick
Rick

15+ years of experience having fun building apps with .NET I began my professional career in 2006, using Microsoft technologies where C# and Windows Forms and WPF were the first technologies I started working with during that time. I had the opportunity to actively participate in the Windows ecosystem as an MVP and Windows 8/Windows Phone application developer from 2013-2018. Throughout my career, I have used Azure as my default cloud platform and have primarily worked with technologies like ASP.NET Core for multiple companies globally across the US, UK, Korea, Japan, and Latin America. I have extensive experience with frameworks such as: ASP.NET Core Microsoft Orleans WPF UWP React with TypeScript Reactive Extensions Blazor I am an entrepreneur, speaker, and love traveling the world. I created this blog to share my experience with new generations and to publish all the technical resources that I had been writing privately, now made public as a contribution to enrich the ecosystem in which I have developed my career.