【C#】LINQ のメソッド名が SQL っぽいからラップする

SQL チックなメソッド名

C# の LINQ to Object(以下、単に LINQ と記載) はとても便利なんですけど、メソッド名が SQL っぽくて、いまいち直感的ではない上にダサいです。例として、JavaScript と比較してみましょう。

LINQ でのメソッド名 JavaScript のメソッド名
Select map
Where filter
OrderBy sort

以下は、実際に利用する例です。
JavaScript ではこう。

const hoge = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
const fuga = hoge.filter(x => x % 2 === 0).map(x => x * 2).sort();

C# ではこう。

var hoge = new[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
var fuga = hoge.Where(x => x % 2 == 0).Select(x => x * 2).OrderBy(x => x).ToArray();

もちろん慣れてくれば Select だろうが Where だろうがスラスラ読めるのですが、慣れていなければ引っかかるかもしれません。一方、map, filter, sort というのは直感的で意味がわかりやすいです。

ラップする

ということで、ラッパーメソッドで名前を変えます。

Select => Map

public static IEnumerable<TResult> Map<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> func)
    => source.Select(x => func(x));

public static IEnumerable<TResult> Map<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, int, TResult> func)
    => source.Select((x, index) => func(x, index));

Where => Filter

public static IEnumerable<T> Filter<T>(this IEnumerable<T> source, Func<bool> func)
    => source.Where(x => func());

public static IEnumerable<T> Filter<T>(this IEnumerable<T> source, Func<T, bool> func)
    => source.Where(x => func(x));

public static IEnumerable<T> Filter<T>(this IEnumerable<T> source, bool func)
    => source.Where(x => func);

OrderBy => Sort

public static IOrderedEnumerable<TSource> Sort<TSource, TKey>(this IEnumerable<TSource> sources, Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
    => sources.OrderBy(keySelector, comparer);

public static IOrderedEnumerable<TSource> Sort<TSource, TKey>(this IEnumerable<TSource> sources, Func<TSource, TKey> keySelector)
    => sources.OrderBy(keySelector);

public static IOrderedEnumerable<TSource> SortByDesc<TSource, TKey>(this IEnumerable<TSource> sources, Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
    => sources.OrderByDescending(keySelector, comparer);

public static IOrderedEnumerable<TSource> SortByDesc<TSource, TKey>(this IEnumerable<TSource> sources, Func<TSource, TKey> keySelector)
    => sources.OrderByDescending(keySelector);

ソートに関しては、元の OrderBy が、昇順か降順かでメソッドが別れています。昇順のソートが OrderBy、降順のソートが OrderByDescending なので、それぞれ SortSortByDesc というメソッド名でラップしましたが、ここは昇順か降順かをオプション引数で切り替える Sort という名前のメソッドにしても良いでしょう。

「昇順か降順かをメソッド名だけで判断できる」という利点はありますが、同じようなことが別の名前のメソッドで定義されているのも、個人的には気持ち悪いと感じます。

同様に、ThenByThenByDescendingThen というメソッドでラップしてみます。

public static IOrderedEnumerable<TSource> Sort<TSource, TKey>(this IEnumerable<TSource> sources, Func<TSource, TKey> keySelector, IComparer<TKey> comparer, bool byDesc)
    => byDesc ? sources.OrderByDescending(keySelector, comparer) 
              : sources.OrderBy(keySelector, comparer);

public static IOrderedEnumerable<TSource> Sort<TSource, TKey>(this IEnumerable<TSource> sources, Func<TSource, TKey> keySelector, bool byDesc)
    => byDesc ? sources.OrderByDescending(keySelector)
              : sources.OrderBy(keySelector);

public static IOrderedEnumerable<TSource> Then<TSource, TKey>(this IOrderedEnumerable<TSource> sources, Func<TSource, TKey> keySelector, IComparer<TKey> comparer, bool byDesc)
    => byDesc ? sources.ThenByDescending(keySelector, comparer)
              : sources.ThenBy(keySelector, comparer);

public static IOrderedEnumerable<TSource> Then<TSource, TKey>(this IOrderedEnumerable<TSource> sources, Func<TSource, TKey> keySelector, bool byDesc)
    => byDesc ? sources.ThenByDescending(keySelector)
              : sources.ThenBy(keySelector);

オプション引数 byDesc を設定しました。これにより、次のようにコードを書けます。

// ラップ前
var hoge = GetEmployee.OrderBy(s => s.Age).ThenByDesc(x => x.Salary);

// ラップ後
var hoge = GetEmployee.Sort(s => s.Age).Then(s => s.Salary, byDesc: true);

もちろん引数名の byDesc を省略することもできますが、理由があり記載しています。理由については、以下の記事で解説しているので、よかったらご覧ください。

【C#】あえて引数名を書くパターン

比較

メソッド名をラップしてみることで、冒頭のコードは次のように書けるようになりました。

// 変更前
var hoge = new[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
var fuga = hoge.Where(x => x % 2 == 0).Select(x => x * 2).OrderBy(x => x).ToArray();

// 変更後
var hoge = new[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
var fuga = hoge.Filter(x => x % 2 == 0).Map(x => x * 2).Sort(x => x).ToArray();

LINQ にあまり慣れていない人でも、よりスッと入ってきやすいコードになったのではないでしょうか。