こんにちは。働くC#プログラマーのさんさめです。
LINQの中でも使用率が高いFirstOrDefaultメソッドの紹介です。
いやそれくらい、
説明されなくても分かるよ!
…という声が聞こえてきそうですが、
あまり知られてなさそうな省コードな書き方があるので、
ぜひ軽い気持ちで目を通してみてください。
FirstOrDefaultの概要とユースケース
Enumerable.FirstOrDefaultは
配列やコレクションから最初の1要素を取得する
拡張メソッドです。
要素が1つもないときは既定値(default(T))が返ってきます。
intだったら0ですし、stringやクラスなど参照型だったらnullですね。
こんな風に使います。
static void Main(string[] args)
{
// 最初のコマンドライン引数を取得
var firstCommand = args.FirstOrDefault();
// そして取得できたかどうかで処理を分岐…など
if (!string.IsNullOrEmpty(firstCommand)) { /* 省略 */ }
else { /* 省略 */ }
// 以下のような書き方でも取得できるが、
// 配列に要素がないと例外になってしまう。
// var firstCommand = args[0];
}
以下のようなユースケースがあります。
- 要素があるかどうかわからないが、
もしあったら、それを使用して別の処理を行う- 上記のコマンドラインの例など
- 条件に合致する要素を1つだけ取得
- あるModel要素を参照して作られたViewModelを得たい…など
Firstは使っちゃダメ
いきなり余談ですが、Firstメソッドは罠APIなので使わないようにしましょう。
何が罠かというと、要素が見つからない場合にいきなり例外を投げます。
しかも、例外メッセージで「見つかりませんでした」
というようなことを言われてしまうので、
全然本質的な原因を調べることができません。
詳しくは、以下の記事をご覧ください。
【LINQ】Firstは使ってはいけない!FirstOrDefaultを使おう
Whereの直後に使ってないですか?もっと楽に書けますよ
見ていただいた通りFirstOrDefault自体は単純なメソッドです。
ですが、単純すぎるが故に便利さを知らないコードを見かけることがあります。
よく見かけるのが次のようなコードです。
static void Main(string[] args)
{
var intEnumerable = Enumerable.Range(0, 10);
var firstOddNum = intEnumerable
.Where(x => x % 2 == 1) // まずWhereで絞ってから…
.FirstOrDefault(); // FirstOrDefaultで最初の要素を取得
Console.WriteLine(firstOddNum);
}
Whereで絞りこみをしてから、
FirstOrDefaultで取得する、というコードです。
これ、実はコードとしては冗長です。
以下のように書き換えられます。
static void Main(string[] args)
{
var intEnumerable = Enumerable.Range(0, 10);
var firstOddNum = intEnumerable
.FirstOrDefault(x => x % 2 == 1); // 実は直接条件を渡せる
Console.WriteLine(firstOddNum);
}
FirstOrDefaultは要素の条件をFuncで渡すことができます。
Whereで一旦絞る必要がないということですね。
人のコードなんて読みたくないものです…。
読む立場になった時に負担を減らすためにも、
書かなくていいコードは書かないようにしましょう。
趣味で書いてるだけだし、
他人のコードを読むことなんてないけど…
いえいえ、
過去の自分が書いたコードなんて実質他人のコードですよ。
たとえ自分しか使わないコードであったとしても、
省コードにしておく癖をつけることは必ず未来に役立ちます。
FirstOrDefaultは線形探索なのでコストは気を付けるべき
ちなみに、LINQ to Objectの実装は賢いので、
前者の書き方をしたからと言って、
「Whereで絞る処理がシーケンス全体にかかってしまって処理が激重…」
みたいなことにはならないのでご安心ください。
以下のコードは同じ出力結果になります。
(WhereやFirstOrDefaultに渡す条件の処理の中で、
コンソール出力するコードです)
static void Main(string[] args)
{
var intEnumerable = Enumerable.Range(0, 10);
bool predicate(int x)
{
Console.WriteLine(x);
return x % 2 == 1;
}
Console.WriteLine("Whereの後にFirstOrDefault");
var firstOddNum = intEnumerable
.Where(predicate)
.FirstOrDefault();
Console.WriteLine(firstOddNum);
Console.WriteLine("\n条件渡しFirstOrDefault");
firstOddNum = intEnumerable
.FirstOrDefault(predicate);
Console.WriteLine(firstOddNum);
}
実行結果は、以下の通りです。
どちらの書き方でも、
列挙の要素すべては走査していないことが分かります。
(もし全部走査してたら0~9まで出力されてから1が出力されるはず)
FirstOrDefaultは該当する要素が見つかったら、
そこですぐに打ち切ってくれる特徴があるということですね。
逆に言うとやってることはただの線形探索なので、
条件に該当する要素が末尾の方にある場合ばっかりな時は
毎回全要素なめてしまう可能性があるということです。
「該当する要素が後ろに集中しそう…」
とアタリがついている場合は、
LastOrDefaultに置き換えるだけでも意外と効くかもしれません。
まとめ
今回はFirstOrDefaultの紹介を行いました。
- リストや配列の先頭の要素を取得する関数
- 条件を渡すことでWhereを省略できることがある
- 条件を渡す場合は不当に重くなっていないか意識する
LINQは気軽に使えますが、
その分実際にどんな処理が走っているかを
気にしづらいので、
意図せず重い処理になることがあります。
そのあたりの話も追々していきたいですね。
最後までお読みいただきありがとうございました。
関連記事です。
Firstを使うのだけは絶対にやめましょう。
FirstOrDefault一択です。
コメント