Ever wish you could act on different types of variables — effectively switching by object type and taking different action depending on which class is present? Discriminated Unions from functional programming languages offer an answer to this. In this article I explore the good and bad of Discriminated Unions in C# and offer some thoughts on whether this might be right for your project.
Discriminated Unions are a functional programming convenience that indicates that something is one of several different types of objects. For example, a User might be an unauthenticated user, a regular user, or an administrator.
While discriminated unions are being evaluated for addition to the C# language, they are not presently available, however, the OneOf library provides an alternative for those wanting to use discriminated unions before we have official language support for it.
Let me show you how it works and why you may or may not want to use it.
How OneOf Works
Returning a OneOf Result
This example is from a hobbyist game development project I’m tinkering with. I needed a routine that has a technician attempt to work on a work item. Depending on a random number result, they will either get a
ProgressMadeResult, or a
Note: I ask that you suspend your disbelief as to whether or not these classes should even exist and look at this as purely a technical demonstration of OneOf
Let’s unpack this. The only really unusual part about this code is the method’s return type:
OneOf<WorkCompletedResult, ProgressMadeResult, SetbackResult>.
This is, frankly, a fairly ugly way of using generics to say that the result of the method will be one of the different types of generic arguments.
From there, as long as the method returns one of these results no code changes are needed.
Working with OneOf Results
So, now that we have a method returning a
OneOf result, what can we do with this?
The following example is from a node in a behavior tree I’m building (stay tuned for a future article on more details on behavior trees in general). This code switches off of the result of the call too the
GetWorkOnItemResult method defined above and handles each case differently.
.Switch function takes an
Action parameter for each generic type defined, giving you access to a strongly-typed instance of that object type as we see with the use of the
setback parameter, for example.
These arguments are matched in order to the order that the generic types are defined on the
OneOf type declaration. In our example, the first parameter will be an
Action<WorkCompletedResult>, the second will be an
Action<ProgressMadeResult>, and the third will be
Action<SetbackResult>. This is why we can use
setback.ProgressMade, a property declared on the
OneOf lets us return one of several different options and then gives you a method to do something different depending on which one of those types you encounter.
If you needed to structure your code so that each case returns a value, you can use
Match instead of
Switch as follows:
As you can see, this is nearly identical to
Match uses a
Func<T, TResult> instead of an
So, this is an interesting trick, but where does it fall down?
Unlike languages like TypeScript and F#, you can’t define a simple reusable alias for a Discriminated Union, meaning that if you pass around the same combinations of types, you have to use the same
OneOf syntax on return types and parameter values and you can't use a simple type alias.
For reference: a type alias for a Discriminated Union in TypeScript looks like this:
The code completion is very limited on the
Map functions. The most irritating aspect is having to remember the ordering of types in the
If you declare two different
OneOf return types with the parameters in different orders, they will not be swappable between each other
Acting only on a Specific Type
Let’s say you want to look at a result and only do something if the return is a
StetbackResult. You could either use
Switch with empty parameters, or you can use the
IfT1 members. Sadly, these are their actual names, so it becomes easy to mix up which type is in which order yet again.
So, this is something you can do in C#. The more important question is: Should you?
My answer to that, after investigating the library is: probably not. It’s a cool trick, but the language constraints hamstring the viability of any library.
For now, unless you have some very targeted usages, my recommendation is that you avoid the added complexity of
OneOf and either wait for official language support or add a small F# library and reference it from C# or VB .NET code.
If you have other thoughts or know of another way of getting Discriminated Unions work better in C#, please let me know.
Originally published at https://dev.to on September 22, 2019.