Our journey to F#: Equality for free

We had one kind of defects in our C# that was very annoying: hidden usage of equality and missing equality members on involved classes.

We invested a lot of time to get rid of these in our C# code. Luckily, in F#, this problem does not exist because of the compiler.

But, let’s start at the beginning…

The problem with equality in C#

Imagine that somewhere there is this line of code:

items.Union(additionalItems)

items and additionalItems are some enumerables, and we want the union of these, two. Union checks the resulting set for duplicates and removes them.

But to identify duplicates, equality must be implemented in a meaningful way. As we all know, in C#, the default implementation is reference equality.

Since our business logic uses data that was passed from the client and data that is read from the database, we often have the same data, but in two different objects. If we want them to be treated as equally, we need to implement the equality members. Of course, it’s obvious!

Not so much, when a line of code – like the Union above – is “hidden” in some code you are calling, either your code or a library. Or even worse, via a generic class: SomeGeneric<MyClassThatShouldImplementEqualityButIDidntKnow>

Let’s do something about it

So we did several things to get a grip on this problem:

  • Code analyzer that prohibits the usage of Union, Distinct, GroupBy, Except, Intersect, Contains and Equals.
  • Extension methods constraining the passed values to IEquatable for the prohibited methods mentioned above. So classes have to implement IEquatable. And we have a unit test that checks all classes implementing IEquatable for correctness.
  • Explicit equality comparer when equality was context-sensitive.
  • Equals.Fody when we needed real implementations for equality on a class (as little as possible because of the build time penalty this introduces)

Manually implementing the equality members (or with the help of R#) is very time consuming, not refactoring friendly and error prone.

So it is a pain!

And in F#?

In F#, however, the compiler generates the equality members for records automatically. So all the pain simply goes away. Records have a reasonable default behaviour, compared to classes.

A side note: almost all our classes in C# are immutable because of theses reasons.

Almost.

A mix of C# and F#

Our code base is now a mix of C# and F# code. So when we are in a mixed context, equality still is a bit tricky. For us, this happens in the tests, when a test uses classes and records mixed in an object-graph. But luckily, there is FluentAssertions , and we wrote some wrappers so that it can easily be used from F#. But this is another blog post.

Find all blog posts about our journey to F# here.

This blog post is made possible with the support of Time Rocket, the product this journey is all about. Take a look (German only).

About the author

Urs Enzler

2 comments

By Urs Enzler

Recent Posts