When to Use SingleOrDefault vs FirstOrDefault? Many developers are familiar with the FirstOrDefault() method in LINQ for C#. However, there’s also SingleOrDefault().

What’s the key difference between them? And why might you choose one over the other instead of always sticking with FirstOrDefault()?

Both methods work on collections that implement the IEnumerable<T> interface, allowing you to query data like lists or database results. The main distinction comes down to how they handle the number of items in the collection.

Here’s a quick comparison:

Number of ItemsFirstOrDefault() BehaviorSingleOrDefault() Behavior
0 itemsReturns the default value for the typeReturns the default value for the type
1 itemReturns that itemReturns that item
More than 1 itemReturns the first itemThrows an InvalidOperationException: “Sequence contains more than one element”

To see this in action, you can try running some simple code yourself:

List<int?> numbers = new List<int?>();
Console.WriteLine("SingleOrDefault: {0}", numbers.SingleOrDefault());
Console.WriteLine("FirstOrDefault: {0}", numbers.FirstOrDefault());

numbers = new List<int?> { 1 };
Console.WriteLine("SingleOrDefault: {0}", numbers.SingleOrDefault());
Console.WriteLine("FirstOrDefault: {0}", numbers.FirstOrDefault());

numbers = new List<int?> { 1, 2 };
Console.WriteLine("SingleOrDefault: {0}", numbers.SingleOrDefault());
Console.WriteLine("FirstOrDefault: {0}", numbers.FirstOrDefault());

The output should look like this:

SingleOrDefault: 
FirstOrDefault: 
SingleOrDefault: 1
FirstOrDefault: 1
Unhandled exception. System.InvalidOperationException: Sequence contains more than one element
   at System.Linq.ThrowHelper.ThrowMoreThanOneElementException()

(Note: The blank lines for the empty list represent null, which is the default for nullable types like int?. For non-nullable types, it would be something like 0 for int.)

In most scenarios, we don’t want to throw an exception just because a collection has multiple itemsโ€”that’s why FirstOrDefault() is often the go-to choice. It safely grabs the first item or defaults if nothing is there.

When Should You Use SingleOrDefault()?

SingleOrDefault() shines in situations where you’re expecting exactly one result, and anything else indicates a problem. Here are a couple of common use cases:

  1. When uniqueness is guaranteed: For instance, if you’re querying a database by a unique identifier like a GUID for a user ID, you should only get one record back. If more than one appears, it could signal data corruption or a bug, so throwing an exception is helpful for catching that early.
  2. For validation after changes: After updating or inserting data, you might want to verify that exactly one item meets certain criteria. SingleOrDefault() can act as a quick assertion to confirm the system’s state is as expected.

Additionally, if you’re dealing with scenarios where duplicates shouldn’t existโ€”like looking up a single configuration setting or a unique entity in a cacheโ€”SingleOrDefault() enforces that expectation.

A Common Misconception About SingleOrDefault()

I’ve seen discussions on forums like Stack Overflow where people claim SingleOrDefault() scans the entire collection, which could be inefficient for large datasets. That’s not accurate.

It actually only checks up to the second item: if it finds more than one, it throws the exception right away without iterating further. To illustrate, here’s a simplified look at the source code for SingleOrDefault():

private static TSource? TryGetSingle<TSource>(this IEnumerable<TSource> source, out bool found)
{
    if (source is null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
    }

    if (source is IList<TSource> list)
    {
        switch (list.Count)
        {
            case 0:
                found = false;
                return default;
            case 1:
                found = true;
                return list[0];
        }
    }
    else
    {
        using IEnumerator<TSource> e = source.GetEnumerator();
        if (!e.MoveNext())
        {
            found = false;
            return default;
        }

        TSource result = e.Current;
        if (!e.MoveNext())
        {
            found = true;
            return result;
        }
    }

    found = false;
    ThrowHelper.ThrowMoreThanOneElementException();
    return default;
}

In the else block, you can see it advances the enumerator just twice at mostโ€”once for the first item and once to check for a second. This makes it efficient, as it doesn’t waste time on the rest of the collection.

Summary

In summary, choose FirstOrDefault() for flexibility when multiple results are possible, and opt for SingleOrDefault() when you need to enforce singularity in your data queries. Understanding this can help prevent subtle bugs and make your code more robust.

References


Leave a Reply

Your email address will not be published. Required fields are marked *