【C#】IComparerを実装して独自のルールでコレクションをソートする

List<T>.Sort()メソッドを使用する際、デフォルトの並び順(昇順など)ではなく、「特定のプロパティに基づいて並び替えたい」「特殊な計算式で順序を決めたい」という場合があります。

クラス自体にIComparable<T>を実装するとそのクラスの「既定の順序」が変わってしまいますが、IComparer<T>インターフェイスを実装したクラスを別途作成し、それをSortメソッドに渡すことで、その場限りのソート順を適用することができます。

ここでは、日付の異なるスケジュールデータを、「日付に関係なく、時刻(時・分)が早い順」に並び替えるシナリオを例に解説します。


目次

IComparer<T>インターフェイスの概要

IComparer<T>は、2つのオブジェクトを比較するためのメソッドCompareを提供します。

int Compare(T x, T y)

戻り値のルールはIComparable<T>と同様です。

  • 負の値: x は y より小さい(前)
  • 0: x と y は等しい
  • 正の値: x は y より大きい(後)

実践的なコード例:日付を無視した時刻順ソート

以下のコードは、数日間にわたる予定リストを、朝早い順(時刻順)に並び替えるために、専用の比較クラスTimeOfDayComparerを作成して使用する例です。

using System;
using System.Collections.Generic;

namespace ScheduleApp
{
    class Program
    {
        static void Main()
        {
            // 日付はバラバラのスケジュールリスト
            var schedules = new List<DateTime>
            {
                new DateTime(2025, 10, 1, 19, 30, 0), // 10/1 19:30
                new DateTime(2025, 10, 2, 09, 00, 0), // 10/2 09:00
                new DateTime(2025, 10, 1, 12, 15, 0), // 10/1 12:15
                new DateTime(2025, 10, 3, 08, 45, 0), // 10/3 08:45
            };

            Console.WriteLine("--- 通常のソート(日時順) ---");
            schedules.Sort(); // デフォルトでは日付も含めて比較される
            foreach (var dt in schedules)
            {
                Console.WriteLine(dt.ToString("MM/dd HH:mm"));
            }

            Console.WriteLine("\n--- カスタムソート(時刻のみ順) ---");
            // 自作したComparerのインスタンスを渡してソート
            schedules.Sort(new TimeOfDayComparer());
            
            foreach (var dt in schedules)
            {
                Console.WriteLine(dt.ToString("MM/dd HH:mm"));
            }
        }
    }

    /// <summary>
    /// DateTimeの日付部分を無視し、時刻のみで比較を行うクラス
    /// </summary>
    public class TimeOfDayComparer : IComparer<DateTime>
    {
        public int Compare(DateTime x, DateTime y)
        {
            // TimeOfDayプロパティ(TimeSpan型)を取得して比較
            // これにより、年・月・日が異なっていても、時刻だけで大小判定が行われます。
            return x.TimeOfDay.CompareTo(y.TimeOfDay);

            // 参考: 時(Hour)だけで比較したい場合は以下のように記述します
            // return x.Hour.CompareTo(y.Hour);
        }
    }
}

実行結果

--- 通常のソート(日時順) ---
10/01 12:15
10/01 19:30
10/02 09:00
10/03 08:45

--- カスタムソート(時刻のみ順) ---
10/03 08:45
10/02 09:00
10/01 12:15
10/01 19:30

技術的なポイントと応用

1. 比較ロジックの再利用性

IComparer<T>を実装したクラス(ここではTimeOfDayComparer)を作成するメリットは、比較ロジックを部品として再利用できる点です。複数の場所で同じソート順が必要な場合や、ユニットテストを行う際に非常に便利です。

2. Comparison<T>デリゲートによる簡略化

もし、その比較ロジックが一回限りで使い回す予定がない場合、わざわざクラスを定義せずにラムダ式(Comparison<T>デリゲート)を使って簡潔に書くことも可能です。

// ラムダ式で直接比較ロジックを渡す
schedules.Sort((x, y) => x.TimeOfDay.CompareTo(y.TimeOfDay));

3. LINQのOrderByとの違い

List<T>.Sortはリスト自体を並び替えます(インプレース処理)。一方、LINQのOrderByメソッドは元のリストを変更せず、並び替えられた新しいシーケンスを返します。 OrderByでもIComparer<T>を使用できます。

// LINQで使用する場合
var sortedQuery = schedules.OrderBy(x => x, new TimeOfDayComparer());

まとめ

IComparer<T>を実装することで、オブジェクトのデフォルトの順序とは異なる、独自の視点でのソートが可能になります。「日付無視」「大文字小文字無視」「特定のID順」など、要件に応じた柔軟な並び替えを実現するための標準的な手法です。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次