The Trouble with .Single()
Tags: dotnet, csharp
Published: 2024-01-03 13:49:19

---
title: The Trouble with .Single()
published: true
description: Checking for a singleton safely
tags: #dotnet #csharp
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2024-01-03 13:47 +0000
---

_*in this article I will use list to refer to any enumerable collection not just List\<T\>_
## The O.G. Singleton

In computing it can often be beneficial to work with lists. They are null-safe by default as an empty list is always valid. From time to time we will expect that a list we are working with will contains one _and only one_ value. The correct mathematical term for this is `Singleton`, a term which may be confusing now given the rise in popularity of IoC containers and service lifetimes, but take this as another reminder that _context always matters_.

Out of the box, LINQ gives us two methods for accessing the single item in a singleton: `.Single()` and `.SingleOrDefault()`. This has made me very angry and should be widely regarded as a bad move.

## Single()

IFF the list contains a single item (or a single item matching a given predicate) `Single()` will return that item. Else, this method will throw an exception. Which is great for halting program execution, but not so great for error handling. We have methods of proving that a list contains only one item to us and other developers, but not great ways of proving it to the compiler.

```csharp
if (items.Count() != 1) return;

// A little while later
return items.Single();
```

In the example above we know we've checked and that single will not throw, but the compiler is unaware of this assumption, and so, if the check ever gets removed, there's nothing letting the developer know that this assumption is not longer true. It's possible to solve this with a type

```csharp
public class Singleton<T>
{
  public T Item { get; }
  private Singleton(T item) => Item = item;
  public bool TryParse(
    IEnumerable<T> list,
    [NotNullWhen(true)]
    Singleton<T>? singleton)
  {
    singleton = null;
    if (list.Count() != 1) return false;

    singleton = new(list.Single());
    return true;
  }
}
```

Yes, yes, I know I've used `Single()` in the type I've said would let us stop using `Single()` but the enforcement of this constraint is the raison d'etre of this type and so I think it's pretty clear to later developers that remove the check here is a breaking change. This is a perfectly workable solution for older .NET code, but we can (and will) do better later on.

## SingleOrDefault()

A popular alternative, which does not throw is `SingleOrDefault()` which will always return a value. If the list is a singleton, it will return the single item, if not it will return the `default` (null for reference types). Combined with `nullable` enabled, and treating warnings as errors, the compiler will force a null check for you, but that isn't always the case.

## Enter C# 11

List patterns were introduced in C# 11, and they have come to the rescue here. We now have a compiler safe way to enforce either A) that a list is a singleton, or B) that the developer has handled all alternatives.

```csharp
var singleItem = list switch
{
  [] => HandleEmptyList(),
  [var item] => item,
  [var first, var second, ..] => HandleListWithMultipleItems(),
};
```

The compiler is now completely aware of the assumption we are making. Therefore if any of the cases are removed, it will give us an error. Well, a warning that you _really should_ be considering an error.

We can adopt this to a variety of error handling methods, below are examples for `Try`, error handling delegates, and `Result` types.

```csharp
public static bool TrySingle<T>(
  this IEnumerable<T> list,
  [NotNullWhen(true)]
  out T? item)
{
  item = list switch
  {
    [] => null,
    [T singleValue] => singleValue,
    [T first, T second, ..] => null
  };
  return item is not null;
}
```

```csharp
public static T? Single(
  this IEnumerable<T> list,
  Action<string> error)
{
  private T? fail(string errorMsg)
  {
    error(errorMsg);
    return null;
  }

  return list switch
  {
    [] => fail("The list is empty"),
    [T singleItem] => singleItem,
    [T fist, T second, ..] => fail("The list contains multiple entries")
  };
}
```

```csharp
public static Result<T> ParseSingle(this IEnumerable<T> list)
=> list switch
{
  [] => new Error("The list is empty"),
  [T singleItem] => singleItem,
  [T first, T second, ..] => new Error("The list contains multiple entries")
};
```

Note that while each of these methods don't present a significant value from the outside over something like `SingleOrDefault()`, they still have a compiler checked assumption and are thus more resistant to being broken accidentally by later developers.

In your domain code, it's likely much more appropriate to write the `switch` expression out in it's entirety as your domain likely has rules and processes for handling this failed assumption, and if it does not, go check with your customer to find out if it should.