List<T>.Sort()の標準動作とその限界
C#のList<T>(ジェネリックリスト)が提供するSort()メソッド(引数なし)は、要素を「昇順」(intなら数値の大小、stringなら辞書順)に並べ替える便利な機能です。
しかし、このデフォルトのルールでは対応できない、独自の(カスタム)順序で並べ替えたい場合があります。
List<string>を、辞書順ではなく文字列の長さが短い順に並べ替えたい。List<Product>(商品オブジェクト)を、名前順ではなく価格(Price)が安い順に並べ替えたい。
このような場合、Sortメソッドのオーバーロード(引数が異なる同名メソッド)に、Comparison<T>デリゲート(比較メソッド)を渡すことで、ソートのロジックを自由に定義できます。
Comparison<T> デリゲートとは
Comparison<T>は、2つの同じ型(T)のオブジェクト(例: aとb)を受け取り、それらの大小関係をint(整数)で返すメソッドの「型」を定義するデリゲートです。
戻り値のルール: Comparison<T>デリゲート(またはラムダ式)は、以下のルールに従ってint型の値を返す必要があります。
- 負の値(
0未満):aはbよりも小さい(aをbよりも前に並べる)。 - ゼロ(
0):aとbは等しい(順序は問わない)。 - 正の値(
0より大きい):aはbよりも大きい(aをbよりも後に並べる)。
intやstringが持つCompareToメソッドは、このルールに完全に従っているため、カスタムソートの実装に最適です。
List<T>.Sortの動作:インプレース(In-Place)
List<T>.Sort(Comparison<T>)メソッドは、Array.Sortと同様に**インプレース(In-Place)**で動作します。
これは、新しいリストを返すのではなく、メソッドを呼び出した元のリストインスタンス自体を直接変更することを意味します。戻り値はvoidです。
コード例1:List<string>を「長さ順」でソート
List<string>を、デフォルトの辞書順ではなく、各文字列の「長さ(Length)」が短い順(昇順)に並べ替えます。
この比較ロジックは、Sortメソッドの引数として「ラムダ式」で渡すのが最も簡潔です。
using System;
using System.Collections.Generic;
public class ListSortByLengthExample
{
public static void Main()
{
var taskNames = new List<string>
{
"Review Document",
"Fix Login Bug",
"Deploy",
"Write Unit Tests"
};
Console.WriteLine("--- 元の順序 ---");
PrintList(taskNames);
// --- 昇順(長さの短い順)でソート ---
// 比較ロジックをラムダ式で渡す
// (a, b) => a.Length.CompareTo(b.Length)
// aの長さがbの長さより短ければ負、長ければ正が返る
taskNames.Sort((a, b) => a.Length.CompareTo(b.Length));
Console.WriteLine("\n--- 長さ昇順 (短い順) ---");
PrintList(taskNames);
// --- 降順(長さの長い順)でソート ---
// 比較ロジック (a と b) を逆転させる
taskNames.Sort((a, b) => b.Length.CompareTo(a.Length));
Console.WriteLine("\n--- 長さ降順 (長い順) ---");
PrintList(taskNames);
}
private static void PrintList(List<string> list)
{
foreach (var item in list)
{
Console.WriteLine(item);
}
}
}
出力結果:
--- 元の順序 ---
Review Document
Fix Login Bug
Deploy
Write Unit Tests
--- 長さ昇順 (短い順) ---
Deploy
Fix Login Bug
Write Unit Tests
Review Document
--- 長さ降順 (長い順) ---
Review Document
Write Unit Tests
Fix Login Bug
Deploy
コード例2:オブジェクトのリストをプロパティでソート
Comparison<T>は、オブジェクトの特定のプロパティ(キー)に基づいてソートする際に非常に強力です。
using System;
using System.Collections.Generic;
// 商品を表すクラス
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
}
public class ListSortByPropertyExample
{
public static void Main()
{
var products = new List<Product>
{
new Product { Name = "Monitor", Price = 30000m },
new Product { Name = "Keyboard", Price = 8500m },
new Product { Name = "Mouse", Price = 4200m }
};
Console.WriteLine("--- 元の順序 ---");
PrintList(products);
// 商品の「Price」(価格) プロパティに基づいて昇順ソート
products.Sort((p1, p2) => p1.Price.CompareTo(p2.Price));
Console.WriteLine("\n--- 価格が安い順 (昇順) ---");
PrintList(products);
}
private static void PrintList(List<Product> list)
{
foreach (var p in list)
{
Console.WriteLine($"{p.Name} (価格: {p.Price:N0}円)");
}
}
}
出力結果:
--- 元の順序 ---
Monitor (価格: 30,000円)
Keyboard (価格: 8,500円)
Mouse (価格: 4,200円)
--- 価格が安い順 (昇順) ---
Mouse (価格: 4,200円)
Keyboard (価格: 8,500円)
Monitor (価格: 30,000円)
まとめ
List<T>.Sort()メソッドの引数なし版は昇順ソートしかできませんが、Comparison<T>デリゲート(通常はラムダ式)を引数として渡すことで、ソートのルールを自由に定義(カスタマイズ)できます。
a.CompareTo(b)(昇順)またはb.CompareTo(a)(降順)のパターンと、LengthやPriceのような任意のプロパティを組み合わせることで、C#のリストを柔軟に並べ替えることが可能です。
