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順」など、要件に応じた柔軟な並び替えを実現するための標準的な手法です。
