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.