Firstは使ってはいけない罠API
こんにちは、現役C#プログラマーのさんさめです。
C#を書くなら絶対に使うべきLINQですが、
実は使ってはいけない罠APIがあります。
そのうちの一つが、
列挙の先頭の要素を返してくれる、
First(Enumerable.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警察になりました。
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を使いましょうね!)
コメント