Error-Free C# Part II: Functional Data Processing
Tags: csharp, functional
Published: 2020-09-28 15:13:27

---
title: Error-Free C# Part II: Functional Data Processing
published: true
date: 2020-09-17 00:00:00 UTC
tags: ["csharp", "functional"]
canonical_url: https://webbureaucrat.gitlab.io/posts/error-free-csharp-part-ii-functional-data-processing/
---

Mutability bugs and thread-unsafety are big problems in data processing. Fortunately, the .NET Framework has strong support for immutable collections, eliminating entire categories of bugs. This post will show how to use [extension methods](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods) to create even safer ways to interact with with lists in C# by building on the [IMaybe monad type](https://webbureaucrat.gitlab.io/posts/error-free-csharp-part-i-the-maybe-monad/) we created in the previous post in this series.

### Prerequisite: System.Collections.Immutable

This post builds on the lovely [Systems.Collections.Immutable](https://docs.microsoft.com/en-us/dotnet/api/system.collections.immutable?view=netcore-3.1) library in C#. Much digital ink has already been spilt in praise of the concept of immutable collections, and I don't have anything to add, but if you're not yet familiar, I recommend reading this [detailed post on the benefits thereof](https://docs.microsoft.com/en-us/archive/msdn-magazine/2017/march/net-framework-immutable-collections).

This post will also use extension methods. You don't necessarily need to already be familiar with extension methods, but if you need a supplement,I recommend [this Microsoft article](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods).

### The need for safety in data processing

C#'s immutable collections lead us well on our way on our functional programming journey, but it gives us little protection at the margins. How do we protect ourselves in case of empty lists? How can you be sure that you have enough `try`/`catch` blocks to handle the dreaded [ArgumentOutOfRangeException](https://docs.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception?view=netcore-3.1)?

These issues are every bit as [avoidable](https://webbureaucrat.gitlab.io/posts/error-free-csharp-part-i-the-maybe-monad/) as `NullReferenceException`, and with a little extension method magic, we'll soon forget we ever had them.

### Set up the extension methods class

Start by instantiating a new class. Extension methods must live in static classes, and, by convention, that static class should end in "Extensions". I'm going to call mine _EnumerableExtensions_ and apply it to the broad`IEnumerable<T>` interface (of which `IImmutableList<T>` is an implementation).

_EnumerableExtensions.cs_

```csharp
public static class EnumerableExtensions
{
}
```

### Extension method signatures

Next, let's write the signature for our extension methods. Extension method signatures use the `this` keyword in the parameter list, which allows us to use it as a normal object method instead of a static method.

```csharp
public static class EnumerableExtensions
{ 
    public static IMaybe<T> MaybeFirst<T>(this IEnumerable<T> xs)
        => throw new NotImplementedException(); 

    public static IMaybe<T> MaybeLast<T>(this IEnumerable<T> xs)
        => throw new NotImplementedException();
}
```

### The safest way to wrap methods

Now let's implement methods, performing checks here so we'll be sure to have safely wrapped monads.

```csharp
public static class EnumerableExtensions
{ 
    public static IMaybe<T> MaybeFirst<T>(this IEnumerable<T> xs)
        => xs.Any() 
        ? Maybe.Factory<T>(xs.First()) 
        : new None<T>(); 

    public static IMaybe<T> MaybeLast<T>(this IEnumerable<T> xs)
        => xs.Any() ? Maybe.Factory<T>(xs.Last()) : new None<T>();
}
```

There are a few ways we could have handled these two cases. We could have used`Count()` to see if the count is greater than one, but if the `IEnumerable`is very massive, it could take a long time to execute. We could also have wrapped the operation in a `try` and caught `System.InvalidOperationException`, but throwing an exception just to catch it is somewhat computationally expensive.

We could also have called `Maybe.Factory(...)` directly using `FirstOrDefault()`, but this would have produced unexpected results if the default value of the type isn't `null`, as is the case with primitive-like types like `DateTime` and `int`.

`Any()`, then, is the best practice here. So as long as we stick to our "Maybe" extension methods and avoid `First()` and `Last()`, we can feel confident that we are always using the best practice.We've just eliminated `System.InvalidOperationException`.

### Eliminating `System.ArgumentOutOfRangeException` from `ElementAt()`

While we're here, let's add one more extension method to eliminate exceptions coming from `ElementAt()`. `ElementAt()` fails if there are fewer elements than the requested index.

In this case, `Any()` doesn't help us, and `Count()` is still pretty computationally expensive, so for this, simply catching the exception will suffice.

```csharp
public static class EnumerableExtensions
{ 
    public static IMaybe<T> MaybeElementAt<T>(this IEnumerable<T> xs, int i) 
    { 
        try { return Maybe.Factory<T>(xs.ElementAt(i)); } 
        catch { return new None<T>(); } 
    } 
 
    public static IMaybe<T> MaybeFirst<T>(this IEnumerable<T> xs)
        => xs.Any() ? Maybe.Factory<T>(xs.First()) : new None<T>(); 

    public static IMaybe<T> MaybeLast<T>(this IEnumerable<T> xs)
        => xs.Any() ? Maybe.Factory<T>(xs.Last()) : new None<T>();
}
```

### Never worry about missing data again.

As long as we stick to our extension methods, we always know we're using the safest, most efficient, and most correct way to get specific elements of a list.

This concludes the part of the series on IMaybe monads and eliminating exceptions relating to missing expected data. In my [next post](https://webbureaucrat.gitlab.io/posts/safer-data-parsing-with-try-monads/), I'll work on eliminating other kinds of runtime exceptions using a special kind of monad called a _try_ monad.