コレクションの先頭要素へのアクセス
C#で配列やリストなどのコレクションを扱う際、「最初の1件だけを取得したい」あるいは「特定の条件に合致する最初の1件を探したい」という場面は頻繁に発生します。
LINQ(Language Integrated Query)は、この目的のためにFirstメソッドとFirstOrDefaultメソッドを提供しています。これらは似た機能を持っていますが、対象の要素が見つからなかった場合の挙動に決定的な違いがあります。
この記事では、これら2つのメソッドの動作の違いと、バグを防ぐための正しい使い分けについて解説します。
Firstメソッド:要素が「必ず存在する」場合
Firstメソッドは、コレクションの先頭の要素、または条件に一致する最初の要素を返します。
このメソッドの最大の特徴は、要素が見つからない場合に例外(InvalidOperationException)をスローする点です。
したがって、Firstは「ロジック上、その要素は確実に存在するはずだ(存在しなければシステムとして異常だ)」という前提がある場合にのみ使用します。
コード例:Firstの使用
using System;
using System.Collections.Generic;
using System.Linq;
public class FirstExample
{
public static void Main()
{
var idList = new List<int> { 101, 205, 308, 410, 500 };
// 1. 引数なし: リストの「最初」の要素を取得
int firstId = idList.First();
Console.WriteLine($"最初のID: {firstId}");
// 2. 条件指定: 「300より大きい」最初の要素を取得
int targetId = idList.First(n => n > 300);
Console.WriteLine($"300より大きい最初のID: {targetId}");
// 3. 要素が見つからない場合 (例外発生)
try
{
// 600より大きい要素は存在しないため、例外がスローされる
int errorId = idList.First(n => n > 600);
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"エラー発生: {ex.Message}");
}
}
}
出力結果:
最初のID: 101
300より大きい最初のID: 308
エラー発生: Sequence contains no matching element
FirstOrDefaultメソッド:要素が「ないかもしれない」場合
FirstOrDefaultメソッドも同様に最初の要素を探しますが、要素が見つからなかった場合に例外をスローせず、その型の「既定値(デフォルト値)」を返します。
- 参照型(
string, クラスなど)の既定値:null - 数値型(
int,doubleなど)の既定値:0 bool型の既定値:false
検索結果が存在しない可能性がある場合(例:ユーザー検索、特定条件のデータ抽出)は、FirstではなくFirstOrDefaultを使用することで、安全に処理を続行できます。
コード例:FirstOrDefaultの使用
using System;
using System.Collections.Generic;
using System.Linq;
public class FirstOrDefaultExample
{
public static void Main()
{
var productCodes = new List<string> { "A-001", "B-102", "C-205" };
// 1. 条件に一致する要素がある場合
string? result1 = productCodes.FirstOrDefault(s => s.StartsWith("B"));
Console.WriteLine($"'B'で始まるコード: {result1}");
// 2. 条件に一致する要素がない場合
// "Z" で始まるコードはないため、null が返される (例外は出ない)
string? result2 = productCodes.FirstOrDefault(s => s.StartsWith("Z"));
// null合体演算子 (??) を使って、見つからなかった場合の表示を指定
Console.WriteLine($"'Z'で始まるコード: {result2 ?? "<見つかりませんでした>"}");
}
}
出力結果:
'B'で始まるコード: B-102
'Z'で始まるコード: <見つかりませんでした>
値型における注意点と対策
FirstOrDefaultをintなどの値型で使用する場合、要素が見つからなかったときは 0 が返されます。しかし、これでは「検索結果としての 0」なのか「見つからなかったことを示す 0」なのか区別がつかない場合があります。
このような場合、以下の2つの対策が有効です。
- null許容型(
int?)にキャストしてから検索する: こうすることで、見つからない場合はnullが返るようになります。 - この後解説する
Anyで存在確認をする: 先に有無を確認してからFirstを呼び出します(ただし、処理コストは2回分になります)。
int[] numbers = { 0, 10, 20 };
// 通常の FirstOrDefault (見つからない場合 0)
int resultA = numbers.FirstOrDefault(n => n > 100); // 0
// int? にキャストしてからの FirstOrDefault (見つからない場合 null)
int? resultB = numbers.Cast<int?>().FirstOrDefault(n => n > 100); // null
if (resultB.HasValue)
{
Console.WriteLine(resultB.Value);
}
else
{
Console.WriteLine("該当なし");
}
まとめ
First():- 要素が必ず存在すると確信できる場合に使用します。
- 存在しない場合は例外が発生し、バグとして検知できます。
FirstOrDefault():- 要素が存在しない可能性がある場合に使用します。
- 戻り値が既定値(
nullや0)かどうかをチェックする必要があります。
基本的には、予期せぬ例外によるクラッシュを防ぐため、FirstOrDefaultを使用し、戻り値のnullチェックを行うパターンが安全で推奨されます。
