C# の LINQ で先頭を取る First と FirstOrDefault

FirstFirstOrDefault はシーケンスの先頭要素を取得するメソッドだ。両者の違いは、要素が見つからない場合の動作にある。

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);  // 8

FirstOrDefault:要素がなければデフォルト値

First と同様だが、要素が見つからない場合にデフォルト値を返す。

int[] numbers = { 1, 3, 5, 7, 9 };

int firstEven = numbers.FirstOrDefault(n => n % 2 == 0);
Console.WriteLine(firstEven);  // 0(int のデフォルト値)

偶数がないので、int のデフォルト値である0が返る。

空のシーケンスでの違い

ここが両者の最も重要な違いだ。

First

要素がなければ InvalidOperationException をスロー

FirstOrDefault

要素がなければデフォルト値(参照型は 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? になる。

使い分けの指針

どちらを使うべきかは状況による。

First を使う場面

要素が必ず存在することがわかっている場合。存在しないのはプログラムのバグなので、例外で検出したい。

FirstOrDefault を使う場面

要素が存在しない可能性があり、その場合はデフォルト値で処理を続けたい。

// ユーザー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&lt;User&gt;
{
    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デフォルト値その要素を返す例外

パフォーマンスの考慮

FirstFirstOrDefault は最初の要素が見つかった時点で探索を終了する。大きなコレクションでも効率的だ。

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&lt;int&gt; を返す
var taken = numbers.Take(1);

First はスカラー値、Take(1) はシーケンスを返す。単一の値が欲しいなら First / FirstOrDefault を使う方が適切だ。