C# の LINQ で先頭を取る First と FirstOrDefault
First と FirstOrDefault はシーケンスの先頭要素を取得するメソッドだ。両者の違いは、要素が見つからない場合の動作にある。
First:先頭要素を取得
シーケンスの最初の要素を返す。
int[] numbers = { 5, 3, 8, 1, 9 };
int first = numbers.First();
Console.WriteLine(first); // 5条件を指定すれば、その条件を満たす最初の要素を取得できる。
int[] numbers = { 5, 3, 8, 1, 9 };
int firstEven = numbers.First(n => n % 2 == 0);
Console.WriteLine(firstEven); // 8FirstOrDefault:要素がなければデフォルト値
First と同様だが、要素が見つからない場合にデフォルト値を返す。
int[] numbers = { 1, 3, 5, 7, 9 };
int firstEven = numbers.FirstOrDefault(n => n % 2 == 0);
Console.WriteLine(firstEven); // 0(int のデフォルト値)偶数がないので、int のデフォルト値である0が返る。
空のシーケンスでの違い
ここが両者の最も重要な違いだ。
要素がなければ InvalidOperationException をスロー
要素がなければデフォルト値(参照型は null、値型は 0 など)を返す
var empty = new List<int>();
// int first = empty.First(); // 例外!
int firstOrDefault = empty.FirstOrDefault(); // 0デフォルト値の指定(.NET 6 以降)
.NET 6 以降では、戻り値のデフォルト値を明示的に指定できる。
int[] numbers = { 1, 3, 5 };
// 偶数がなければ -1 を返す
int result = numbers.FirstOrDefault(n => n % 2 == 0, -1);
Console.WriteLine(result); // -1これにより、0(int のデフォルト値)と「見つからなかった」を区別できる。
参照型での注意点
参照型の場合、FirstOrDefault は要素がなければ null を返す。
var people = new List<Person>
{
new Person { Name = "Alice", Age = 25 },
new Person { Name = "Bob", Age = 30 }
};
Person? child = people.FirstOrDefault(p => p.Age < 18);
// child は null
// null チェックが必要
if (child != null)
{
Console.WriteLine(child.Name);
}Nullable 参照型を有効にしている場合は、戻り値の型が Person? になる。
使い分けの指針
どちらを使うべきかは状況による。
要素が必ず存在することがわかっている場合。存在しないのはプログラムのバグなので、例外で検出したい。
要素が存在しない可能性があり、その場合はデフォルト値で処理を続けたい。
// ユーザーIDは必ず存在するはず → First
var user = users.First(u => u.Id == currentUserId);
// 検索結果は見つからない可能性がある → FirstOrDefault
var result = products.FirstOrDefault(p => p.Name.Contains(searchTerm));
if (result == null)
{
Console.WriteLine("該当商品なし");
}Single / SingleOrDefault との違い
Single は要素が「ちょうど1つ」であることを期待する。
var users = new List<User>
{
new User { Id = 1, Name = "Alice" },
new User { Id = 2, Name = "Bob" }
};
// ID=1 のユーザーは1人だけ
var user = users.Single(u => u.Id == 1);
// 複数見つかると例外
// var users30 = users.Single(u => u.Age == 30); // 例外| メソッド | 要素なし | 要素1つ | 要素複数 |
|---|---|---|---|
| First | 例外 | 最初を返す | 最初を返す |
| FirstOrDefault | デフォルト値 | 最初を返す | 最初を返す |
| Single | 例外 | その要素を返す | 例外 |
| SingleOrDefault | デフォルト値 | その要素を返す | 例外 |
パフォーマンスの考慮
First と FirstOrDefault は最初の要素が見つかった時点で探索を終了する。大きなコレクションでも効率的だ。
var numbers = Enumerable.Range(1, 1000000);
// 最初の要素を即座に返す(100万件を走査しない)
int first = numbers.First();ただし Where(...).First() のように組み合わせた場合でも、条件を満たす最初の要素で止まる。遅延評価の恩恵だ。
Take(1) との違い
1件だけ取得するなら Take(1) でも似たことができる。
int[] numbers = { 1, 2, 3, 4, 5 };
// First: int を返す
int first = numbers.First();
// Take(1): IEnumerable<int> を返す
var taken = numbers.Take(1);First はスカラー値、Take(1) はシーケンスを返す。単一の値が欲しいなら First / FirstOrDefault を使う方が適切だ。