C# Tips | コレクション・LINQ:条件付き並び替え

C# C#
スポンサーリンク

はじめに:「条件付き並び替え」は“状況に応じてソートルールを切り替える”技

業務システムでは、単純に「いつも同じ並び順」だけで済むことはあまりありません。

画面のソート条件をユーザーが選べる
検索条件によって、並び順を変えたい
フラグが 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# エンジニア”に一歩近づけます。

タイトルとURLをコピーしました