【LINQ】Max,Minを使うときに気を付けたい事2選

C#

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

LINQの中でも解説が最も不要であろうメソッドの1つが、
MaxMinでしょう。

その名の通り、リストの中の
最大値、最小値を返すメソッドです。

ただの解説記事の場合ここで終わっても
おかしくないです。

ですが、Max,Minは
使う際に気を付けなければいけない
やや直感的に使いにくい点が2つあります。

  • 要素が無いリストに使うと例外になる
  • クラスに使うと数値そのものしか返ってこない

本記事では、
上記の2点について解説します。

スポンサーリンク

要素が無いリストに使うと例外になる

いきなりどでかい罠です。
要素が1つも無いリストに対して
このメソッドを呼び出すと例外
(InvalidOperationException)になります。

実際にはこんなコードは書きませんが

この罠挙動、
どこかで見たことがありますね。
そう、Firstです。

Firstについては以下の記事をご覧ください。
【LINQ】Firstは使ってはいけない!FirstOrDefaultを使おう

つまり、必ず事前に要素が存在するかの
チェックを行わなければなりません。

var list = new[]
{
    2,
    4,
    6,
    8,
    10
};

var oddList = list.Where(x => x % 2 == 1);
if (oddList.Any())
{
    Console.WriteLine($"奇数の中の最大値は{oddList.Max()}です");
}
else
{
    Console.WriteLine("奇数は無かった");
}
さんさめ
さんさめ

これが面倒なのよ…

とはいえ、じゃあ無かった時は何を返せば良いのか、
という話にはなるので例外にするしかなかったのかもしれません。

ただ、使う側からすると
いきなりアプリが落ちるのは迷惑な話です。
番兵のように、
その型の最小値(Minの場合は最大値)を返すメソッドだけでも
あったらありがたかったと思うんですけどね。

// 例えばこういうの。全型分用意するのが大変なのでわざわざ作らないけれども
public static int MaxOrGuard(this IEnumerable<int> source, int guard = int.MinValue) 
    => source.Any() ? source.Max() : guard;

クラスに使うと数値そのものしか返ってこない

Max、Minは、特定の条件を満たさない限り、
自作クラスや整数型以外にはそのままは使えません。

ですが、Max,Minには変換関数をかますことのできる
便利なオーバーロードがあります。

var list = new[]
{
    new Card("Spade", 1),
    new Card("Clover", 2),
    new Card("Diamond", 3),
    new Card("Heart", 4),
};

// 変換関数を噛ますとクラスの情報は失われてしまう
Console.WriteLine(list.Max(x => x.Number)); // 4

このオーバーロード、一見便利なのですが、
あくまでも変換した後にその最大値を返すだけなので、
クラスそのものを返すメソッドが存在しません。

あくまで、list.Select(x => x.Number).Max()
の簡略形でしかないということです。

つまり、上記の例でいうと、
「最大の数字を持っているカード」
を取得するLINQメソッドは存在しないのです。

そういうことをしたい場合は、
比較したいクラスがIComparable<T>
実装している必要があります。

// これがあれば…
public class Card : IComparable<Card>
{
    public int CompareTo(Card other) => Number.CompareTo(other.Number);

    public int Number { get; set; }
    public string Mark { get; set; }
    public override string ToString() => $"{Mark} : {Number}";
}

var list = new[]
{
    new Card("Spade", 1),
    new Card("Clover", 2),
    new Card("Diamond", 3),
    new Card("Heart", 4),
};

// こうしてOK
Console.WriteLine(list.Max()); // Heart : 4
さんさめ
さんさめ

普通に面倒だし、そもそも
手出しできないクラスの場合はお手上げ

ただし、.Net 6 以降であれば、
MaxByというまさに求める挙動をするメソッドが
追加されています。

.Net Frameworkには残念ながら存在しないので、
本家のコードを参考に実装すると良いでしょう。

このように、クラスに使おうと思うと
用途によってはひと手間いることに注意が必要です。

まとめ

まとめです。

  • Max,Minはリストの最大値、最小値を求める
  • 要素の無いリストに使うと例外になる
  • クラスに使おうとするとひと手間いる

最後までお読みいただきありがとうございました。

コメント

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