Null許容型でLINQでSumやらAverageするときに注意すべきコト


どー言うことか?

IEnumerable<Nullable<T>>に対して、Sum()やらAverage()やら使うと個人的に予想外の結果になったのでその備忘録。

Nullableに関して

NumericなNullableに対して、BinaryOperationを実行した場合、片っぽがnullなら伝播して、結果はnullに成る。


int? x=42;
int? y=114514;
int? z=null;

//114556が出力される。
Console.WriteLine((x + y)?.ToString() ?? "NULL");

//NULLが出力される。
Console.WriteLine((x + z)?.ToString() ?? "NULL");

で、本題

さて、Ienumerable<int?>見たいなシーケンスにSumやらAverageを適用したらどうなるだろうかってのが今日のお題。
Nullが紛れ込んでなきゃ何の問題も無いけど、紛れ込んでいた場合を検証してみた。


int?[] nonNullArray=Enumerable.Range(0,10).Select(x=>(int?)x).ToArray();
int?[] nullArray=nonNullArray.ToArray();
nullArray[9]=null;

int?[] allNullArray=Enumerable.Repeat((int?)null,10).ToArray();

//45
Console.WriteLine(nonNullArray.Sum());

//4.5
Console.WriteLine(nonNullArray.Average());

//9
Console.WriteLine(nonNullArray.Max());

//0
Console.WriteLine(nonNullArray.Min());

Console.WriteLine();

//36
Console.WriteLine(nullArray.Sum());

//4
Console.WriteLine(nullArray.Average());

//8
Console.WriteLine(nullArray.Max());

//0
Console.WriteLine(nullArray.Min());

Console.WriteLine();

//0
Console.WriteLine(allNullArray.Sum());

//null
Console.WriteLine(allNullArray.Average());

//null
Console.WriteLine(allNullArray.Min());

//null
Console.WriteLine(allNullArray.Max());

概ねこんなかんじになる。
以上のことから、

  • 要素にnullが有る場合、フィルタされる。
    • Sum()の場合、nullはフィルタされるので、nullの要素以外の合計となる。
    • Average()の場合、上記の合計をnull以外の要素数で除した結果となる。
    • Min()Max()は不感
  • すべての要素がnullの場合、Sum()は0を返す。
  • Average(),Max(),Min()nullを返す。

まとめ

以上の検証から、nullの要素は基本的にフィルタされて処理されることがわかった。
全要素がnullの場合は、Sum()は0を返し、Sum()以外はnullを返すので、ちょいと注意が必要かと。

要素にnullが存在する場合、結果がnullに成ることを企図したければ、事前にAnyあたりで、当たりを付けるか、Aggregateで処理するか、素直にforeachに展開するが良いかと思います。