We use cookies and other tracking technologies to improve your browsing experience on our site, analyze site traffic, and understand where our audience is coming from. To find out more, please read our privacy policy.

By choosing 'I Accept', you consent to our use of cookies and other tracking technologies.

We use cookies and other tracking technologies to improve your browsing experience on our site, analyze site traffic, and understand where our audience is coming from. To find out more, please read our privacy policy.

By choosing 'I Accept', you consent to our use of cookies and other tracking technologies. Less

We use cookies and other tracking technologies... More

Login or register
to apply for this job!

Login or register
to publish this job!

Login or register
to save this job!

Login or register
to save interesting jobs!

Login or register
to get access to all your job applications!

Login or register to start contributing with an article!

Login or register
to see more jobs from this company!

Login or register
to boost this post!

Show some love to the author of this blog by giving their post some rocket fuel 🚀.

Login or register to search for your ideal job!

Login or register to start working on this issue!

Login or register
to save articles!

Engineers who find a new job through WorksHub average a 15% increase in salary 🚀

Blog hero image

Error-Free C# Part II: Functional Data Processing

Eleanor Holley 3 February, 2021 | 3 min read

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 to create even safer ways to interact with with lists in C# by building on the IMaybe monad type we created in the previous post in this series.

Prerequisite: System.Collections.Immutable

This post builds on the lovely Systems.Collections.Immutable 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.

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.

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?

These issues are every bit as avoidable 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 broadIEnumerable<T> interface (of which IImmutableList<T> is an implementation).

EnumerableExtensions.cs

public static class EnumerableExtensions
{
}
Join our newsletter
Join over 111,000 others and get access to exclusive content, job opportunities and more!

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.

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.

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 usedCount() to see if the count is greater than one, but if the IEnumerableis 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.

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, I'll work on eliminating other kinds of runtime exceptions using a special kind of monad called a try monad.

Originally published on webbureaucrat.gitlab.io

Author's avatar
Eleanor Holley
an enthusiastic functional programmer.
    Elm
    ReasonML
    C#
    Scala

Related Issues

cosmos / gaia
  • Started
  • 0
  • 4
  • Intermediate
  • Go
cosmos / gaia
  • Started
  • 0
  • 3
  • Intermediate
  • Go
cosmos / ibc
  • Open
  • 0
  • 0
  • Intermediate
  • TeX
cosmos / ibc
cosmos / ibc
  • Started
  • 0
  • 1
  • Intermediate
  • TeX
viebel / klipse-clj
viebel / klipse-clj
  • Started
  • 0
  • 4
  • Intermediate
  • Clojure
viebel / klipse
  • Started
  • 0
  • 1
  • Intermediate
  • Clojure
viebel / klipse
  • 1
  • 2
  • Intermediate
  • Clojure
viebel / klipse
  • Started
  • 0
  • 4
  • Intermediate
  • Clojure
  • $80

Get hired!

Sign up now and apply for roles at companies that interest you.

Engineers who find a new job through WorksHub average a 15% increase in salary.

Start with GitHubStart with Stack OverflowStart with Email