Photo by Markus Spiske on Unsplash

OneOf<Us> Discriminated Unions in C#

Matt Eland
4 min readSep 22, 2019

--

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 WorkCompletedResult, a ProgressMadeResult, or a SetbackResult.

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.

The .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 SetbackResult instance.

So, 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 Switch except Match uses a Func<T, TResult> instead of an Action<T>.

Weak Areas

So, this is an interesting trick, but where does it fall down?

Type Aliases

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:

Code Completion

The code completion is very limited on the Switch and Map functions. The most irritating aspect is having to remember the ordering of types in the OneOf definition.

Order Swapping

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 AsT1 and IfT1 members. Sadly, these are their actual names, so it becomes easy to mix up which type is in which order yet again.

My Opinion

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.

--

--

Matt Eland

Microsoft MVP in AI, AI Specialist at Leading EDJE. Author of "Refactoring with C#".