空のシーケンスで Min/Max/Average を使うときの注意点
コレクションの要素が Where 句の内容などの絞り込みの結果、要素が空になってしまったとき、 Min(), Max(), Average() といったシーケンスの最小値、最大値、平均値を計算すると InvalidOperationException の例外が発生してしまいます。
具体的には、以下のコードのとおり。
where 句の結果、要素が0件になってしまい Average の実行で InvalidOperationException が発生します。
これをどう対処するのか、というのがこの記事の内容です。
public class Hello { public static void Main() { var samples = new List<int>() { 1,2,3,4,5,6,7,8,9,10 }; // System.InvalidOperationException: // Sequence contains no elements var avg = samples.Where(p => p > 10) .Average(p => p); System.Console.WriteLine($"{avg}"); } }
補足として、シーケンスの要素の型が null を許容する型の場合は例外ではなく null の値を返却します。以下の場合だと avg の値は null になり、最後の出力は -1 。
つまり、この記事は、値型だけの話になります。
public class Hello { public static void Main() { var samples = new List<int?>() { 1,2,3,4,5,6,7,8,9,10 }; var avg = samples.Where(p => p > 10) .Average(p => p); System.Console.WriteLine($"{avg ?? -1}"); } }
対策
要素が存在しない可能性があるときは DefaultIfEmpty() を間にはさみます。
事前の LINQ 結果が空になったとき、要素型のデフォルト値、または、指定した値を渡します。
以下の場合だとコレクションの型は int 型なのでデフォルト値は「0」です。
public class Hello { public static void Main() { var samples = new List<int>() { 1,2,3,4,5,6,7,8,9,10 }; var avg = samples.Where(p => p > 10) .DefaultIfEmpty() .Average(p => p); System.Console.WriteLine($"{avg}"); } }
その他(補足)
「いちろぐ - LINQ - 空のシーケンスでMin/Max/Averageを使ったときのメモ」では、try-catch 句で例外をキャッチする対策を指摘しています。
public class Hello { public static void Main() { var samples = new List<int>() { 1,2,3,4,5,6,7,8,9,10 }; try { var avg = samples.Where(p => p > 10) .Average(p => p); System.Console.WriteLine($"{avg}"); } catch(Exception e) { // ERROR System.Console.WriteLine($"{e}"); } } }
大体の場合では、例外処理は、すこし大げさではないかと思います。
例外は「通常はありえない」ことが起きたときに処理されるブロックです。別の言い方だと「メソッドの定める結果を達成できないなら例外を投げる」というのもあります。
where 句の結果、0件というのは、「想定できる」こと、または、「通常ありえる」なら DefaultIfEmpty() を利用したほうが適切である可能性は高そうです。
where 句の結果、0件になると、「定める結果を達成できない」なら例外でもよいのかもしれません。
気軽な例外にならないように注意。
参考
- 作者:Paolo Pialorsi;Marco Russo
- 発売日: 2014/07/02
- メディア: Kindle版