Selecting Single Values from IEnumerable

When working with LINQ and IEnumerable<T>, there are various methods to select a single element from a sequence. Each method serves a specific purpose and has different behaviors depending on whether the collection contains multiple matching elements, no elements, or is empty. In this lesson, we will explore the most common methods used to retrieve a single element.

Selecting Elements

ElementAt / ElementAtOrDefault

  • ElementAt(int index) retrieves the element at the specified index in the sequence. It throws an exception if the index is out of bounds.

  • ElementAtOrDefault(int index) retrieves the element at the specified index, but if the index is out of bounds, it returns the default value (e.g., null for reference types).

var numbers = new List<int> { 1, 2, 3, 4, 5 };

// Using ElementAt
int numberAtIndex2 = numbers.ElementAt(2); // returns 3
int numberOutOfRange = numbers.ElementAtOrDefault(10); // returns 0 (default for int)
  • Use ElementAt when you know the index is valid.
  • Use ElementAtOrDefault when you're unsure if the index will exist in the collection. If it's not there, you'll get the default value for the specified type (either null for reference types or the default value for value types).

First / FirstOrDefault

  • First returns the first element of a sequence. It throws an exception if the sequence is empty.
  • FirstOrDefault returns the first element of a sequence, or the default value (e.g., null for reference types) if the sequence is empty.
var numbers = new List<int> { 1, 2, 3, 4, 5 };

// Using First
int firstNumber = numbers.First(); // returns 1
int emptyFirst = new List<int>().First(); // throws an exception

// Using FirstOrDefault
int firstOrDefaultNumber = numbers.FirstOrDefault(); // returns 1
int emptyFirstOrDefault = new List<int>().FirstOrDefault(); // returns 0 (default for int)

Single / SingleOrDefault

  • Single returns the single element of a sequence. It throws an exception if the sequence is empty or if there is more than one matching element.
  • SingleOrDefault returns the single element of a sequence, or the default value (e.g., null for reference types) if the sequence is empty. It throws an exception if more than one element exists.
var singleNumberList = new List<int> { 42 };

// Using Single
int singleNumber = singleNumberList.Single(); // returns 42
int noNumber = new List<int>().Single(); // throws an exception
int multipleNumbers = new List<int> { 1, 2 }.Single(); // throws an exception

// Using SingleOrDefault
int singleOrDefaultNumber = singleNumberList.SingleOrDefault(); // returns 42
int emptySingleOrDefault = new List<int>().SingleOrDefault(); // returns 0 (default for int)
int multipleSingleOrDefault = new List<int> { 1, 2 }.SingleOrDefault(); // throws an exception

First, FirstOrDefault, Single, and SingleOrDefault can take an optional predicate

Taking our slightly contrived list example above - here's the behavior when we use a predicate to check if the number is even:

var singleEvenNumberList = new List<int> { 41, 42 };

// Using Single
int singleNumber = singleEvenNumberList.Single(n => n % 2 == 0); // returns 42
int noNumber = new List<int>().Single(n => n % 2 == 0); // throws an exception
int multipleNumbers = new List<int> { 1, 2, 3, 4 }.Single(n => n % 2 == 0); // throws an exception

// Using SingleOrDefault
int singleOrDefaultNumber = singleEvenNumberList.SingleOrDefault(n => n == 0); // returns 42
int emptySingleOrDefault = new List<int>().SingleOrDefault(n => n == 0); // returns 0 (default for int)

int multipleSingleOrDefault = new List<int> { 1, 2, 3, 4 }.SingleOrDefault(n => n % 2 == 0); // throws an exception

Here Be Dragons with First / FirstOrDefault

While First and FirstOrDefault can be useful, they come with a caveat: they return the first matching element in a sequence, even if multiple elements match the condition. That sounds reasonable since it's called First after all, but it's a code smell. This can introduce subtle bugs if you assume the collection only contains one matching element but it actually contains more.

🌶️🌶️🌶️ In most scenarios, Spencer overwhelmingly prefers to use Single or SingleOrDefault. These methods are explicit in their expectations: they demand that the sequence contains only one matching element, which can help avoid subtle bugs and improve code clarity. By using Single, you can ensure that your logic enforces uniqueness, avoiding cases where multiple elements are incorrectly returned.

Example

var employees = new List<Employee>
{
    new Employee { Id = 1, Name = "John" },
    new Employee { Id = 1, Name = "Jane" }
};

// Using First
Employee firstEmployee = employees.First(e => e.Id == 1); // returns "John"

// Potentially dangerous assumption:
Employee singleEmployee = employees.FirstOrDefault(e => e.Id == 1); // returns "John", but there are two employees with Id == 1

In this case, First would return the first employee it finds ("John"), but it ignores that there is another employee with the same ID ("Jane"). If your logic assumes there should only be one match, using Single would be a safer option, as it would throw an exception if multiple matches are found.

🌶️🌶️🌶️ Some developers go too far to avoid exceptions in their code, to the point of writing software that is technically incorrect. Exceptions are not bad - it's better to have an exception thrown than to introduce a subtle bug that puts a production system in an incorrect state.