はじめに:「カスタムソート」は“業務ルールをそのまま並び順にする技”
「優先度は High → Middle → Low の順」
「ステータスは 未処理 → 処理中 → 完了 の順」
「部署コードは A, B, C の順ではなく、営業 → 開発 → 管理 の順」
こういう“人間が決めた順番”って、数値の昇順・降順では表現できません。
この「業務独自の順番」をコードに落とし込むのが、カスタムソート(カスタム並び替え)です。
ここでは、初心者でも書けるように
- 「優先度を High → Middle → Low の順に並べる」
- 「ステータスを任意の順番で並べる」
- 比較ロジックをユーティリティ化して再利用する
という流れで、かみ砕いて説明していきます。
基本アイデア:「並び順を数値に変換してからソートする」
発想:業務の順番を「ランク(順位)」にしてしまう
例えば、優先度を表す文字列 "High", "Middle", "Low" があるとします。
これを「High → Middle → Low の順に並べたい」とき、
そのまま文字列の昇順・降順ではうまくいきません。
そこでやることは一つです。
「High を 1、Middle を 2、Low を 3 のように“ランク(順位)”に変換してから、その数値でソートする」
これがカスタムソートの基本パターンです。
例1:優先度 High → Middle → Low の順に並べる
データモデルを用意する
まずは簡単なクラスを用意します。
public class TaskItem
{
public string Title { get; set; } = "";
public string Priority { get; set; } = ""; // "High", "Middle", "Low"
}
C#データを作ります。
var tasks = new List<TaskItem>
{
new TaskItem { Title = "A", Priority = "Low" },
new TaskItem { Title = "B", Priority = "High" },
new TaskItem { Title = "C", Priority = "Middle" },
new TaskItem { Title = "D", Priority = "High" },
};
C#優先度を「ランク」に変換する関数を書く
ここが一番大事なところです。
業務ルールを「数値に変換する関数」として切り出します。
public static int PriorityRank(string priority)
{
return priority switch
{
"High" => 1,
"Middle" => 2,
"Low" => 3,
_ => 99 // 想定外は一番後ろに
};
}
C#ここでの重要ポイントは、「業務の順番を“数字の小さいほうが先”という形にマッピングしている」ことです。
High が一番優先度が高いので 1、Middle が 2、Low が 3。
想定外の値は 99 にして、最後尾に追いやるようにしています。
ランクをキーにしてソートする
あとは、このランクを OrderBy のキーに使うだけです。
var sorted = tasks
.OrderBy(t => PriorityRank(t.Priority));
foreach (var t in sorted)
{
Console.WriteLine($"{t.Priority}: {t.Title}");
// High: B
// High: D
// Middle: C
// Low: A
}
C#これで、「High → Middle → Low」の順番が実現できました。
例2:ステータスを「任意の順番」で並べる
ステータスの順番を配列で定義する
今度は、ステータス "New", "InProgress", "Done" を
「New → InProgress → Done」の順に並べたいとします。
まず、「正解の順番」を配列で定義します。
string[] statusOrder = { "New", "InProgress", "Done" };
C#次に、「ステータス文字列を、その配列のインデックスに変換する」関数を作ります。
public static int StatusRank(string status, string[] order)
{
int index = Array.IndexOf(order, status);
return index >= 0 ? index : int.MaxValue; // 見つからなければ最後尾
}
C#ここでの重要ポイントは、「配列の位置=並び順」という考え方です。Array.IndexOf は、「その値が配列の何番目にあるか」を返してくれます。
ステータス順にソートする
データを用意します。
public class Ticket
{
public string Title { get; set; } = "";
public string Status { get; set; } = ""; // "New", "InProgress", "Done"
}
var tickets = new List<Ticket>
{
new Ticket { Title = "A", Status = "Done" },
new Ticket { Title = "B", Status = "New" },
new Ticket { Title = "C", Status = "InProgress" },
new Ticket { Title = "D", Status = "New" },
};
C#ソートします。
var sorted = tickets
.OrderBy(t => StatusRank(t.Status, statusOrder));
foreach (var t in sorted)
{
Console.WriteLine($"{t.Status}: {t.Title}");
// New: B
// New: D
// InProgress: C
// Done: A
}
C#ここでの重要ポイントは、「業務側で“正解の順番”を配列として持ち、それをソートキーに変換している」ことです。
順番を変えたくなったら、statusOrder の中身を変えるだけで済みます。
例3:複数条件のカスタムソート(ステータス → 日付)
「ステータス順 → 同じステータス内では日付の新しい順」
さっきのステータスに加えて、「同じステータスの中では日付の新しい順」にしたいとします。
public class Ticket
{
public string Title { get; set; } = "";
public string Status { get; set; } = "";
public DateTime CreatedAt { get; set; }
}
C#データを用意します。
var tickets = new List<Ticket>
{
new Ticket { Title = "A", Status = "Done", CreatedAt = new DateTime(2026, 2, 10) },
new Ticket { Title = "B", Status = "New", CreatedAt = new DateTime(2026, 2, 12) },
new Ticket { Title = "C", Status = "InProgress", CreatedAt = new DateTime(2026, 2, 11) },
new Ticket { Title = "D", Status = "New", CreatedAt = new DateTime(2026, 2, 13) },
};
C#ソートはこう書けます。
var sorted = tickets
.OrderBy(t => StatusRank(t.Status, statusOrder)) // ステータス順
.ThenByDescending(t => t.CreatedAt); // 同じステータス内では新しい順
C#ここでの重要ポイントは、「カスタムキー(StatusRank)と普通のキー(CreatedAt)を組み合わせている」ことです。OrderBy で“業務ルールの順番”を決め、ThenBy / ThenByDescending で“自然な順番(昇順・降順)”を足していくイメージです。
比較ロジックをユーティリティ化する
「ランク関数」を共通化しておく
同じ優先度やステータスの順番を、あちこちで使うなら、
ランク関数をユーティリティクラスにまとめておくと便利です。
public static class SortOrder
{
private static readonly string[] StatusOrder = { "New", "InProgress", "Done" };
public static int StatusRank(string status)
{
int index = Array.IndexOf(StatusOrder, status);
return index >= 0 ? index : int.MaxValue;
}
public static int PriorityRank(string priority)
{
return priority switch
{
"High" => 1,
"Middle" => 2,
"Low" => 3,
_ => 99
};
}
}
C#使う側は、こう書くだけになります。
var sortedTickets = tickets
.OrderBy(t => SortOrder.StatusRank(t.Status));
var sortedTasks = tasks
.OrderBy(t => SortOrder.PriorityRank(t.Priority));
C#ここでの重要ポイントは、「業務ルールを“名前のついた関数”として外に出しておく」ことです。
これで、「この順番はどこで決まっているのか?」が一目で追えるようになります。
実務で意識してほしいこと
「何順か」を日本語で先に決める
カスタムソートを書く前に、必ず言葉で整理してください。
優先度は High → Middle → Low
ステータスは New → InProgress → Done
部署は 営業 → 開発 → 管理
これが決まれば、あとは「その順番を数値や配列のインデックスに変換する」だけです。
「魔法の if 文」ではなく「変換関数」として切り出す
ありがちな悪い例は、ソートの中に if 文をベタベタ書いてしまうことです。
// 読みにくい例
var sorted = tasks.OrderBy(t =>
{
if (t.Priority == "High") return 1;
if (t.Priority == "Middle") return 2;
if (t.Priority == "Low") return 3;
return 99;
});
C#動きはしますが、テストしづらく、再利用もしにくいです。
これを、
public static int PriorityRank(string priority) { ... }
C#という関数に切り出しておけば、
ソートの行は OrderBy(t => PriorityRank(t.Priority)) とシンプルになり、
ランク関数だけを個別にテストすることもできます。
まとめ:「カスタムソートユーティリティ」は“業務の順番をコードに刻む道具”
カスタムソートの本質は、
「業務で決まっている“人間の順番”を、
数値やインデックスに変換してからソートする」
という一点です。
押さえておきたいポイントは、
業務の順番をまず日本語で決める
その順番を「ランク(順位)」に変換する関数を作るOrderBy(ランク関数) として使う
複数条件があるときは、OrderBy と ThenBy / ThenByDescending を組み合わせる
ここまでできれば、「昇順・降順しか書けない」状態から抜け出して、
“業務ルールそのものを反映した並び順”を、C# と LINQ で自然に表現できるようになります。
