【LINQ】Firstは使ってはいけない!FirstOrDefaultを使おう

スポンサーリンク

Firstは使ってはいけない罠API

こんにちは、現役C#プログラマーのさんさめです。

C#を書くなら絶対に使うべきLINQですが、
実は使ってはいけない罠APIがあります。

そのうちの一つが、
列挙の先頭の要素を返してくれる、
FirstEnumerable.First)メソッドです。

さんさめ自身もC#を書き始めて半年くらいは
Firstメソッドを使っていましたが、
すぐに使うのをやめました。

「え?先頭の要素を返してくれるだけなのに何が罠なの?」
と思ってしまいますよね。

Firstを使ってはいけない理由はたった1つです

Firstは要素が1つもなかった時に例外になる

これに尽きます。
Firstを使うと、たまたま探索した列挙の要素が0だったときに、
即、「Sequence contains no elements」と言われて、
例外になってしまうのです。

下の例は、コマンドライン引数の最初の要素を取得して、
コンソール出力するだけのシンプルなプログラムです。

static void Main(string args[])
{
    // ↓コマンドライン引数が1つもなかった時に例外になる
    var firstCommandArg = args.First(); // InvalidOperationException!!
    Console.WriteLine(firstCommandArg);
}

たった2行だけのシンプルなプログラムですが、
『コマンドライン引数を指定してない』
というなんとも普通の使い方をされただけで、
例外で落ちてしまいます。

本当にあっさりと例外になるので気をつけましょう。
しかも、Firstを使って例外になった時知りたいのは、
なぜ、リストに何もなかったのか?ですよね。

「Sequence contains no elements」
という例外メッセージからは原因が何も分かりません。
(Firstで舐めてるときにはリストが出来上がったあとなので、
当たり前といえば当たり前ですけど)
このこともFirstを使う必然性の無さを高めています。

「じゃあ、Firstを使うときは必ず
try~catchを使わないといけないのか…」
いえいえ、そんなことはありません。
もっと便利なメソッドがあります。

FirstOrDefaultは要素がない時に規定値を返す

FirstOrDefaultを使いましょう

FirstOrDefaultを使いましょう(2回目)

要素がない時にも例外にならずに規定値を返してくれる。
そんな便利なメソッドがFirstOrDefaultです。

規定値とは、default()演算子で囲った時に返ってくる値のことです。

たとえば、intであれば0ですし、boolならfalse。
一般的なクラスやstringならnullとなります。

先程のサンプルコードをFirstOrDefaultを使ったものに
置き換えてみました。

static void Main(string args[])
{
    // FirstOrDefaultなら要素がない場合は規定値を返してくれる
    var firstCommandArg = args.FirstOrDefault(); // この場合はstringの規定値なのでnull
    Console.WriteLine(firstCommandArg);
}

このプログラムならユーザーがどんな使い方をしても、
例外になってしまうことはありません。

さんさめは、FirstOrDefaultを知ってから、
自分のコードのすべてのFirst使用箇所をFirstOrDefaultに置き換え、
今ではFirst警察になりました。

First警察

FirstOrDefaultで受け取った値は必ず有効かチェックすべき

ちょっとだけ話は変わりますが、
先程のプログラムでは、たまたま後段の
Console.WriteLineがnullを渡しても大丈夫なAPIでした。

しかしFirstOrDefaultを使う大抵のケースでは、
後段の処理にそのままつなぐ前に、
規定値との比較を行った方が安全です。

いわゆるnullチェックですね。
(intなどの場合はdefault(int)と比較しなければ意味がないですよ)

せっかくFirstを使わないで例外のリスクを減らしたのに、
nullチェックを忘れて『NullReferenceException』
なんて悲しすぎるので必ずnullチェックしましょう。

例外自体は欲しい時は…

それでも、正しい入力が来なかったことを関数の使用者に伝えたいなど、例外にしたいケースはあると思います。

そんな時はFirstOrDefaultを使用した上で自前で例外を発火しましょう。

前述しましたがFirstで起きる例外には情報量がほぼありません。

渡された列挙を処理するどの段階で要素が0だったのか…適切な情報を出力できるのは他でもない実装者のあなたです。

イレギュラーなケースを握り潰すわけでも、雑な例外に逃げるわけでもない。

そんな細やかな心遣いがアプリケーションの保守性を高めるということですね。

まとめ

今回は以下のことを説明しました。

  • Firstはあっさりと例外を起こす罠APIである
  • FirstOrDefaultを使えば例外にならず規定値が返る
  • FirstOrDefaultを使うなら規定値のチェックも忘れずに

Fisrtではなく、FirstOrDefaultを使うようにして、
落ちないアプリケーションを作りましょう!

(ちなみに、最後の要素を取得するLastも一緒です!
LastOrDefaultを使いましょうね!)

コメント

タイトルとURLをコピーしました