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:

type Record = { Name: string; Age: int }
type Abc =
| A
| B of double
| C of Record

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
void
Specifies a return value type for a method that does not return a value.
HandleAbc
void HandleAbc(Abc abc)
(Abc abc)
{
switch (abc)
{
// Option 1:
case var x
void HandleAbc(Abc abc)
when x.IsA
void HandleAbc(Abc abc)
:
void HandleAbc(Abc abc)
Console
Console
Represents the standard input, output, and error streams for console applications. This class cannot be inherited.
.WriteLine
void Console.WriteLine(string? value)
Writes the specified string value, followed by the current line terminator, to the standard output stream.
value — The value to write.
IOException — An I/O error occurred.
("A");
break;
// Option 2:
case var x
void HandleAbc(Abc abc)
when x ==
void HandleAbc(Abc abc)
Abc
void HandleAbc(Abc abc)
.A
void HandleAbc(Abc abc)
:
void HandleAbc(Abc abc)
Console
Console
Represents the standard input, output, and error streams for console applications. This class cannot be inherited.
.WriteLine
void Console.WriteLine(string? value)
Writes the specified string value, followed by the current line terminator, to the standard output stream.
value — The value to write.
IOException — An I/O error occurred.
("A");
break;
}
switch (abc.Tag
void HandleAbc(Abc abc)
)
{
// Option 3:
case abc.Tags
case abc.Tags.A:
.A
case abc.Tags.A:
:
void HandleAbc(Abc abc)
Console
Console
Represents the standard input, output, and error streams for console applications. This class cannot be inherited.
.WriteLine
void Console.WriteLine(string? value)
Writes the specified string value, followed by the current line terminator, to the standard output stream.
value — The value to write.
IOException — An I/O error occurred.
("A");
break;
}
}

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

Thanks for reading!