C# LINQ FirstOrDefault: Syntax and Defaults
c# linq firstordefault: Learn how C# LINQ FirstOrDefault works, its default value behavior, and common mistakes when using it with sequences.
c# linq firstordefault requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
In C# LINQ, FirstOrDefault returns the first element of a sequence, or a default value if the sequence contains no elements. This method is a practical way to avoid InvalidOperationException when a collection might be empty. However, the default value it returns depends on the type of the elements, which is a common source of confusion for developers.
What FirstOrDefault Returns
The default value returned by FirstOrDefault is determined by the element type:
- For reference types (classes, interfaces, delegates) and nullable value types (
int?,DateTime?, etc.), the default isnull. - For non-nullable value types (
int,bool,DateTime, etc.), the default is the type's default value:0for numeric types,falseforbool,DateTime.MinValueforDateTime, and so on.
Consider this example:
List<int> numbers = new List<int>(); int firstNumber = numbers.FirstOrDefault(); // 0 List<string> names = new List<string>(); string firstName = names.FirstOrDefault(); // null
In the first case, firstNumber is 0 because int is a non-nullable value type. In the second case, firstName is null because string is a reference type.
Basic Syntax and Usage
FirstOrDefault has two overloads:
FirstOrDefault()— returns the first element of the sequence.FirstOrDefault(predicate)— returns the first element that satisfies a condition.
Both overloads return the default value if no element is found. The predicate overload is useful when you need to find a specific item without throwing an exception when it does not exist.
List<Order> orders = GetOrders(); Order pendingOrder = orders.FirstOrDefault(o => o.Status == OrderStatus.Pending);
If no order has a Status of Pending, pendingOrder is null. This avoids the exception that First(predicate) would throw.
FirstOrDefault vs First vs SingleOrDefault
Choosing between these methods depends on the expected number of matching elements:
| Method | Throws when no match | Throws when more than one match | Returns default when no match |
|---|---|---|---|
First | Yes | No | No |
FirstOrDefault | No | No | Yes |
SingleOrDefault | No | Yes | Yes |
First is appropriate when you are certain at least one element exists. SingleOrDefault is for scenarios where the sequence should contain at most one match; it throws if there are multiple matches. FirstOrDefault is the safest choice when you only need the first match and are prepared to handle the absence of a match.
Handling Reference Types and Null
Because FirstOrDefault returns null for reference types, you must check for null before accessing members of the result. A common mistake is to assume the sequence always contains an element and then dereference the result, causing a NullReferenceException.
var user = users.FirstOrDefault(u => u.Id == userId); if (user != null) { Console.WriteLine(user.Name); }
If you do not check for null, the code may fail at runtime when the user is not found. In modern C#, you can use the null-conditional operator to simplify this:
string name = users.FirstOrDefault(u => u.Id == userId)?.Name;
This expression returns null if the user is not found, without throwing an exception.
Value Types and Default Values
For non-nullable value types, the default value is often not meaningful. For example, if you have a list of int and you call FirstOrDefault() on an empty list, you get 0. This can be problematic if 0 is a valid value in your domain. In such cases, consider using int? (nullable) to distinguish between "no element" and "the value is 0".
List<int> scores = new List<int>(); int firstScore = scores.FirstOrDefault(); // 0 int? firstScoreNullable = scores.Select(s => (int?)s).FirstOrDefault(); // null
By converting to a nullable type, you can check for null to detect an empty sequence, which is often more explicit than checking for a sentinel value.
Performance Considerations
FirstOrDefault is efficient for IEnumerable<T> because it stops iterating as soon as it finds a match. For a sequence without a predicate, it returns the first element immediately, so it runs in O(1) time. With a predicate, it iterates until the predicate returns true, so the worst-case time is O(n) if no element matches.
When using LINQ to Entities (Entity Framework), FirstOrDefault is translated to a SQL query that uses TOP 1 (or equivalent), so only one row is fetched from the database. This is generally efficient, but be aware that the predicate is translated to a WHERE clause, so the database can use indexes to speed up the search.
Avoid calling FirstOrDefault on an IEnumerable that is the result of a deferred query multiple times, as each call will re-execute the query. If you need to reuse the result, materialize it with ToList() or ToArray() first.
Common Mistakes and Edge Cases
One common mistake is assuming that FirstOrDefault always returns null for value types. This leads to incorrect logic when checking for a missing element. For example:
int id = ids.FirstOrDefault(i => i == targetId); if (id == 0) // This may also match a legitimate id of 0 { // Handle not found }
A better approach is to use a nullable type or to use Any() before calling First if you need to distinguish between "not found" and a default value.
Another edge case is when the sequence contains null elements. FirstOrDefault returns the first element, which could be null, even if the sequence is not empty. This is often overlooked when using reference types.
List<string> items = new List<string> { null, "a", "b" }; string first = items.FirstOrDefault(); // null
In this case, first is null because the first element is null, not because the sequence is empty. Always consider whether null is a valid element in your collection.
Finally, remember that FirstOrDefault does not throw when the sequence is empty, but it also does not tell you whether the sequence was empty or the first element was the default value. For scenarios where you need to know which case occurred, consider using a nullable type or checking the count before calling the method.