はじめに:「条件付き並び替え」は“状況に応じてソートルールを切り替える”技
業務システムでは、単純に「いつも同じ並び順」だけで済むことはあまりありません。
画面のソート条件をユーザーが選べる
検索条件によって、並び順を変えたい
フラグが ON のときだけ、特定のキーで並び替えたい
こういう「条件によって並び替え方を変える」場面で使うのが、ここでいう 条件付き並び替え です。
ポイントは、
「if 文でゴチャゴチャ書かずに、LINQ のチェーンの中で自然に条件を表現する」
ことです。
ここから、初心者向けにかみ砕いて説明していきます。
基本のおさらい:普通の並び替え(OrderBy / ThenBy)
まずは、条件なしの基本形をサッと確認します。
public class Product
{
public string Name { get; set; } = "";
public decimal Price { get; set; }
public DateTime CreatedAt { get; set; }
}
C#通常の並び替えはこうです。
var ordered = products
.OrderBy(x => x.Price) // 価格昇順
.ThenBy(x => x.Name) // 同じ価格なら名前昇順
.ToList();
C#ここに「条件」をどう混ぜていくか、という話になります。
パターン1:ソート条件を if で切り替える(素直な書き方)
「ユーザーが選んだソート種別」に応じて切り替える
例えば、画面で「価格順」「登録日順」を選べるとします。
public enum SortType
{
ByPrice,
ByCreatedAt
}
C#選択されたソート種別を受け取って、並び替えを変えます。
IEnumerable<Product> SortProducts(
IEnumerable<Product> source,
SortType sortType)
{
switch (sortType)
{
case SortType.ByPrice:
return source
.OrderBy(x => x.Price)
.ThenBy(x => x.Name);
case SortType.ByCreatedAt:
return source
.OrderByDescending(x => x.CreatedAt)
.ThenBy(x => x.Name);
default:
return source;
}
}
C#呼び出し側はこうです。
var ordered = SortProducts(products, SortType.ByPrice).ToList();
C#ここでの重要ポイントは、「“ソート条件ごとに LINQ を書き分ける”のは、最初の一歩として全然アリ」ということです。
まずはこの形で「条件によってソートを変える」感覚を掴むとよいです。
パターン2:条件付きで OrderBy を“挟むかどうか”を変える
「フラグが ON のときだけ、特定のキーで並び替えたい」
例えば、「優先度フラグが ON のときだけ、優先度順に並び替える」ケースを考えます。
public class Task
{
public string Title { get; set; } = "";
public bool IsHighPriority { get; set; }
public DateTime DueDate { get; set; }
}
C#要件はこうです。
優先度ソートが有効なとき
優先度の高いものを先に、その中で期限日昇順
優先度ソートが無効なとき
期限日昇順だけ
素直に if で分けるとこうなります。
IEnumerable<Task> SortTasks(
IEnumerable<Task> source,
bool usePriority)
{
if (usePriority)
{
return source
.OrderByDescending(x => x.IsHighPriority) // true が先
.ThenBy(x => x.DueDate);
}
else
{
return source
.OrderBy(x => x.DueDate);
}
}
C#これでも十分ですが、「共通部分が多くて少しもったいない」と感じるかもしれません。
そこで、「条件付きでキーを変える」書き方もあります。
パターン3:条件を“キーの中に埋め込む”
「優先度をキーに混ぜる」書き方
先ほどの例を、「1 本の LINQ で書く」形にしてみます。
IEnumerable<Task> SortTasks(
IEnumerable<Task> source,
bool usePriority)
{
return source
.OrderBy(x => usePriority ? (x.IsHighPriority ? 0 : 1) : 0)
.ThenBy(x => x.DueDate);
}
C#ここでやっていることはこうです。
usePriority が true のとき
IsHighPriority が true なら 0、false なら 1
→ 優先度高いものが先に来る
usePriority が false のとき
常に 0
→ 全員同じキーなので、この OrderBy は実質「何もしない」に近い
その上で、ThenBy(x => x.DueDate) で期限日昇順にしています。
ここでの重要ポイントは、「“条件付きでソートするかどうか”を、キーの値で表現する」という発想です。
「ソートする/しない」を if で分けるのではなく、「ソートキーが変わる」だけにしてしまうイメージです。
パターン4:条件付きで昇順/降順を切り替える
「チェックボックスで昇順/降順を切り替えたい」
例えば、「価格を昇順/降順で切り替えたい」ケース。
IEnumerable<Product> SortByPrice(
IEnumerable<Product> source,
bool descending)
{
if (descending)
{
return source
.OrderByDescending(x => x.Price)
.ThenBy(x => x.Name);
}
else
{
return source
.OrderBy(x => x.Price)
.ThenBy(x => x.Name);
}
}
C#これも素直で分かりやすいですが、
「同じようなコードが 2 本ある」のが少し気になるところです。
「昇順/降順を条件付きで切り替える」ユーティリティを作る、という手もあります。
パターン5:OrderBy をラップした拡張メソッドを作る
条件付き OrderBy 拡張メソッド
「条件が true のときだけ OrderBy する」拡張メソッドを作ってみます。
using System;
using System.Collections.Generic;
using System.Linq;
public static class OrderByExtensions
{
public static IOrderedEnumerable<TSource> OrderByIf<TSource, TKey>(
this IEnumerable<TSource> source,
bool condition,
Func<TSource, TKey> keySelector)
{
return condition
? source.OrderBy(keySelector)
: source.OrderBy(_ => 0); // 実質「元の順序を維持」
}
public static IOrderedEnumerable<TSource> OrderByDescendingIf<TSource, TKey>(
this IEnumerable<TSource> source,
bool condition,
Func<TSource, TKey> keySelector)
{
return condition
? source.OrderByDescending(keySelector)
: source.OrderBy(_ => 0);
}
}
C#これを使うと、こう書けます。
var ordered = products
.OrderByIf(sortByPrice, x => x.Price)
.ThenBy(x => x.Name)
.ToList();
C#sortByPrice が false のときは、「実質的に元の順序のまま」になります。
ここでの重要ポイントは、「“条件付きでソートする”というパターンを、拡張メソッドとして名前付きにしてしまうと、読みやすさが一気に上がる」ということです。OrderByIf という名前だけで、「条件付きソートだな」と分かります。
パターン6:複数条件を組み合わせた“柔軟ソート”
「画面から複数のソート条件が飛んでくる」ケース
例えば、画面からこんな情報が来るとします。
価格でソートするかどうか
登録日でソートするかどうか
価格は昇順か降順か
これを全部 if で書き分けると、組み合わせ爆発でつらくなります。
そこで、「ソート条件を順番に適用していく」スタイルにします。
IEnumerable<Product> SortProducts(
IEnumerable<Product> source,
bool sortByPrice,
bool priceDescending,
bool sortByCreatedAt)
{
IOrderedEnumerable<Product>? ordered = null;
if (sortByPrice)
{
ordered = priceDescending
? source.OrderByDescending(x => x.Price)
: source.OrderBy(x => x.Price);
}
if (sortByCreatedAt)
{
if (ordered is null)
{
ordered = source.OrderByDescending(x => x.CreatedAt);
}
else
{
ordered = ordered.ThenByDescending(x => x.CreatedAt);
}
}
return (ordered ?? source);
}
C#最初のソート条件が適用されるときだけ OrderBy / OrderByDescending を使い、
2 つ目以降は ThenBy / ThenByDescending を使う、というパターンです。
ここでの重要ポイントは、「“最初のキーだけ OrderBy、それ以降は ThenBy”というルールを守りながら、条件ごとに積み上げていく」ということです。
複数条件ソートを柔軟に組み合わせたいときに使えます。
まとめ:「条件付き並び替え」は“if で分岐するか、キーで表現するか”を選ぶ技
条件付き並び替えの本質は、
状況やフラグに応じて、
どのキーで、どの順番で並び替えるかを切り替える
ことです。
押さえておきたいポイントを整理すると、次の通りです。
素直に if / switch で LINQ を書き分けるのは、最初の一歩として全然アリ
「ソートする/しない」をキーの値(0/1 など)で表現することで、1 本の LINQ にまとめることもできる
昇順/降順の切り替えや「条件付き OrderBy」は、拡張メソッドにすると読みやすくなる
複数条件を組み合わせるときは、「最初だけ OrderBy、以降は ThenBy」というルールを守りながら積み上げる
ここまで腹落ちしていれば、
「とりあえず固定の OrderBy だけ書く」段階から抜けて、
“画面や業務の条件に応じてソートルールを柔軟に切り替えられる C# エンジニア”に一歩近づけます。
