DEV Community

Stuart Lang
Stuart Lang

Posted on • Originally published at stu.dev on

Pattern Matching F# Union Types in C# 7

Starting with C# 7.0, there is basic pattern matching support. I want to look at using this to interop with F# Discriminated Unions, and see what consuming F# code from C# could look like at it's best.

Here is an example of an F# Discriminated Union (hereafter DU)

type Record = { Name: string; Age: int }

type Abc =
    | A
    | B of double
    | C of Record

For those unfamiliar with DUs, the Abc type defined above can be either A, B, or C (and nothing else), and in the case of A there is no "payload", with B and C there is a payload that comes with it.

So given that this compiles down to a sealed class, and that the cases become classes, we can use the new type pattern in C# to match this with:

void HandleAbc(Abc abc)
{
    switch (abc)
    {
        case Abc.B _:
            Console.WriteLine("B");
            break;
        case Abc.C c:
            Console.WriteLine(c.Item.Name);
            break;
    }
}

There are some things to notice here:

1. It's really nice

The code here is clean I'd say, we are matching on type and can either ignore the result of the cast using _ or we can take it as a named variable (like with c in this example).

2. We don't get the safety we have in F

In F#, when handling DUs the compiler ensure that we have handled all cases, in C#, these safety checks aren't enforced.

3. What about case A!

Yeah, so this is where it's not perfect. The F# compiler does generate a type for case A however it is marked as internal and therefore is not accessible for us to match against, however, there are a few options, I'll let you pick which is your favourite:

void HandleAbc(Abc abc)
{
    switch (abc)
    {
        // Option 1:
        case var x when x.IsA:
            Console.WriteLine("A");
            break;
        // Option 2:
        case var x when x == Abc.A:
            Console.WriteLine("A");
            break;
    }

    switch (abc.Tag)
    {
        // Option 3:
        case abc.Tags.A:
            Console.WriteLine("A");
            break;
    }
}

Let me know in the comments what your preference is, or is there a better option?

Thanks for reading!

Top comments (2)

Collapse
 
jeikabu profile image
jeikabu • Edited

Pattern matching was one of my favorite parts of F#.
With records looking like they'll be in c# 8.1 or 8.2, it makes me wonder how close DUs are.
Can you not do case var x when x is Abc.A:? Nevermind, Abc.A looks like it's the value not the type, how odd...

Collapse
 
stuartblang profile image
Stuart Lang

Yeah, DUs are the biggest thing I miss when going back to C#, I would love that.

Can you not do case var x when x is Abc.A:? Nevermind, Abc.A looks like it's the value not the type, how odd...

You're right, the type that gets generated is a little wierd and inuntuative to consume from C#, that's why I created this post, I've been back to it every now and then over the last few months.