This article showcases my ValueCollections C# library.


There is a general trend to refactor to immutability. The simplest reason being: "things that change" are harder to reason about than "things that don't change".

One specific pattern is the Value Object, popularized by Domain-Driven Design (DDD). Value objects are immutable data structures without identity. They're compared based on their contents (value equality), not on their memory location (reference equality).

Current state of C#

With the introduction of C# 9.0 records, it's easier than ever to create value objects. For example, a Point value object can be defined as:

public record Point(float X, float Y);

Aside from being extremely consise, this also automatically provides immutability and value equality. The following test case passes just fine:

[Fact]
public void PointEquality()
{
    // Two dinstinct instances with the same contents:
    var a = new Point(1, 2);
    var b = new Point(1, 2);

    // a.X = 42; // Won't compile. Point is immutable. Yay!

    Assert.Equal(a, b); // Succeeds. Yay!
}

The problem: collections

So far, so good. But now we want to create a Polygon value object that consists of a collection of points. Just like Point, a Polygon is only defined by its contents and has no intrinsic identity. How should we go about that?

The initial reaction could be to: "Just use a record again!":

public record Polygon(List<Point> Points); // Not what we're after.

But, alas, this has some problems:

  • List<T> is mutable. We want our Polygon to be immutable.
  • List<T> doesn't provide value equality. We want our Polygon to be compared based on the contents of its Points.

At the time of writing, .NET has no built-in collection type that provides the properties we're looking for:

  • IReadOnlyList<T> only promises that our reference to it is read-only, but it doesn't guarantee that the contents of the list are immutable. Some other part of the codebase could still have a mutable reference to it and modify the list. Also, it doesn't provide value equality.
  • ImmutableList<T> like the name suggests is immutable, but it doesn't provide value equality. Not to mention its suboptimal performance characteristics.

A solution: ValueCollections

Of course I wouldn't be writing all this if I wasn't trying to push you my ValueCollections library :). It provides a handful of collection types that are both immutable and provide value equality. Just what we're looking for:

using Badeend.ValueCollections;

public record Polygon(ValueList<Point> Points); // Notice the list type.

[Fact]
public void PolygonEquality()
{
    // Two dinstinct instances with the same contents:
    var a = new Polygon([new(3, 1), new(4, 1), new(4, 3)]);
    var b = new Polygon([new(3, 1), new(4, 1), new(4, 3)]);

    // a.Points[0] = new Point(42, 42); // Won't compile. ValueList is immutable. Yay!

    Assert.Equal(a, b); // Succeeds. Yay!
}

Success!

Other features

The ValueCollections are designed to be a zero-overhead drop-in replacement for the System.Collections.Generic.* collections. They mostly have the same API and performance characteristics as their mutable counterparts to make the switch as "boring" as possible. Other notable features:

  • Fully NRT annotated.
  • First-class support for .NET Framework. Even in combination with functionalities not originally present in .NET Framework, such as: Spans & C#12 collection expressions.
  • Support for System.Text.Json & Newtonsoft.Json serialization.
  • Support for loading EntityFramework & EntityFrameworkCore queries directly into ValueCollections. E.g. .ToValueListAsync().
  • Passes the .NET Runtime's own testsuite wherever possible.

See https://badeend.github.io/ValueCollections/ for more information.