FirstOrDefaultとかLastなど単一要素取得のLINQメソッド


はじめに

 LINQ、超便利ですよね!LINQを使わず書いた6、7行のコードが、LINQを使うことで1行で書くことができます。とても簡潔に、そして読みやすくなりますよね。もう自分はLINQなしでC#のコードを書くのは考えられません。

 この投稿では、IEnumerable<T>の中のひとつの要素を取得する次のメソッドを紹介します。

 この投稿では、次のRecordクラスを使います。

サンプルで使う構造体RecordStructとクラスRecordを
public class Record
{
    public int Score { get; set; }
}

 またGetRecordListというList<Record>を取得するメソッドを使います。

ElementAtとElementAtOrDefault

 まず、ElementAtとElementAtOrDefaultについて。この二つのメソッドはint型の引数をとります。引数に渡したint型の数値をインデックスとする要素を返します。負の数やIEnumerable<T>の要素数以上の整数をインデックスとして与えた場合、ElementAtは例外が発生し、ElementAtOrDefaultはT型の既定値が返ってきます。使い方はこんな感じです。

ElementAtの例
List<Record> recordList = GetRecordList ();
Record index2Record = recordList.ElementAt (2);

First

 残り6個のメソッドは、それぞれ2個のオーバーロードがあります。引数を取らないものとデリゲート(Func<T, bool>)を引数にとるものです。Firstメソッドで詳しく紹介します。

 まず引数をとらないものです。下記のコードでは、recordListの先頭要素を取得します。もしrecordListが空の場合、例外が発生します。

Firstの例(1)
List<Record> recordList = GetRecordList ();
Record firstRecord = recordList.First ();

 次に引数(Func<T, bool>)をとるものです。下記のコードでは先頭から調べて、Record型の要素のプロパティScoreが5より大きい最初の要素が返ってきます。

Firstの例(2)
List<Record> recordList = GetRecordList ();
Record overScore5Record = recordList.First (record => record.Score > 5);

 このオーバーロードは、引数に渡したFunc<T, bool>を各要素に適用し、最初にtrueになった要素が取得できます。もしrecordListの要素中に、条件を満たすもの(Func<T, bool>を適用してtrueを返すもの)が無い場合、例外が発生します。

FirstOrDefault

 FirstOrDefaultとFirstはよく似ています。その違いは、要素を取得できなかったときの挙動です。

 先ほどの例だと引数がないFirstでは、recordListが空だった場合例外が発生しました。一方、FirstOrDefaultを使うとnullが返ってきます。FirstOrDefaultはIEnumerable<T>の要素が空の場合、その型の既定値が返ります。(クラス型ならnull)

 同じように引数をとるFirstで、recordList中に条件を満たす要素が一つもなかった場合には例外が発生しました。そのような場合、FirstOrDefaultを使うとnullが返ってきます。これもFirstOrDefaultはIEnumerable<T>の要素で条件を満たす要素がない場合、その型の既定値が返ります。(クラス型ならnull)

LastとLastOrDefault

 LastとLastOrDefaultは、それぞれFirstとFirstOrDefaultとよく似ています。

 First・FirstOrDefaultは最初の要素を返しました。(最初に条件を満たした要素を返すオーバーロードもある)一方、Last・LastOrDefaultは最後の要素を返します。(最後に条件を満たした要素を返すオーバーロードもある)

 サンプルコードは次のような感じです。

Lastの例(1)
List<Record> recordList = GetRecordList ();
Record lastRecord = recordList.Last ();
Firstの例(2)
List<Record> recordList = GetRecordList ();
Record overScore5Record = recordList.Last (record => record.Score > 5);

 LastとLastOrDefaultの違いは、対象の要素が無かった時の挙動の違いで、これはFirstとFirstOrDefaultの違いと一緒です。

SingleとSingleOrDefault

 Singleのサンプルコードは次の通りです。

Singleの例(1)
List<Record> recordList = GetRecordList ();
Record target = recordList.Single ();

 recordListのたった一つの要素を取得します。もし、上記のrecordListの要素数が1で無かった場合、実行時例外が発生します。(これはiOS関連のエラーではなく仕様です。Singleはこういうメソッドなのです。)

 次に引数をとる場合を説明します。

Singleの例(2)
List<Record> recordList = GetRecordList ();
Record overScore5Record = recordList.Single (record => record.Score > 5);

 引数に渡した条件(Func<T, bool>)を満たすたった一つの要素を返します。満たす要素が無かった場合や、二つ以上満たす要素があった場合、例外が発生します。

 SingleとSingleOrDefultの違いは、要素(もしくは条件を満たす要素)が0個だった時の挙動です。その時、Singleでは例外が発生し、SingleOrDefaultではその型の既定値が返ってきます。要素が2個以上(もしくは条件を満たす要素が2個以上)あった場合、どちらも例外が発生するので注意が必要です。

まとめ

 LINQの中でも、IEnumerable<T>から要素を一つ取得するものは、理解しやすく、使いやすく、利用できそうな場面が多いですよね。

補足

 FirstOrDefaultやLastなどこの投稿でとりあげたLINQメソッドは、以前のUnityでは問題がありました。いくつかの条件を満たしてしまった場合、AOTコンパイラの不具合により、iOSなどAOTコンパイルが必要なプラットフォームで実行時例外が発生したのです。

 現在Unityでは、かつてAOTコンパイルが行われていたプラットフォームで、IL2CPPという技術が使われています。

 私が把握している限り、IL2CPPでは本投稿で紹介したメソッドは、問題なく使えているようです。そのため本投稿の記事タイトルを変更し、内容も各メソッドの紹介のみとしました。

 過去の内容を見たい場合は、編集履歴よりご覧ください。