tag:crieit.net,2005:https://crieit.net/tags/LINQ/feed 「LINQ」の記事 - Crieit Crieitでタグ「LINQ」に投稿された最近の記事 2022-06-06T18:50:19+09:00 https://crieit.net/tags/LINQ/feed tag:crieit.net,2005:PublicArticle/18209 2022-06-06T18:50:19+09:00 2022-06-06T18:50:19+09:00 https://crieit.net/posts/wrap-linq-methods 【C#】LINQ のメソッド名が SQL っぽいからラップする <h2 id="SQL チックなメソッド名"><a href="#SQL+%E3%83%81%E3%83%83%E3%82%AF%E3%81%AA%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E5%90%8D">SQL チックなメソッド名</a></h2> <p>C# の <em>LINQ to Object</em>(以下、単に LINQ と記載) はとても便利なんですけど、メソッド名が SQL っぽくて、いまいち直感的ではない上にダサいです。例として、JavaScript と比較してみましょう。<br /> <div class="table-responsive"><table> <thead> <tr> <th>LINQ でのメソッド名</th> <th>JavaScript のメソッド名</th> </tr> </thead> <tbody> <tr> <td>Select</td> <td>map</td> </tr> <tr> <td>Where</td> <td>filter</td> </tr> <tr> <td>OrderBy</td> <td>sort</td> </tr> </tbody> </table></div></p> <p>以下は、実際に利用する例です。<br /> JavaScript ではこう。</p> <pre><code class="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(); </code></pre> <p>C# ではこう。</p> <pre><code class="csharp">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(); </code></pre> <p>もちろん慣れてくれば <code>Select</code> だろうが <code>Where</code> だろうがスラスラ読めるのですが、慣れていなければ引っかかるかもしれません。一方、<code>map</code>, <code>filter</code>, <code>sort</code> というのは直感的で意味がわかりやすいです。</p> <h2 id="ラップする"><a href="#%E3%83%A9%E3%83%83%E3%83%97%E3%81%99%E3%82%8B">ラップする</a></h2> <p>ということで、ラッパーメソッドで名前を変えます。</p> <h3><code>Select</code> => <code>Map</code></h3> <pre><code class="csharp">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)); </code></pre> <h3><code>Where</code> => <code>Filter</code></h3> <pre><code class="csharp">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); </code></pre> <h3><code>OrderBy</code> => <code>Sort</code></h3> <pre><code class="csharp">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); </code></pre> <p>ソートに関しては、元の <code>OrderBy</code> が、昇順か降順かでメソッドが別れています。昇順のソートが <code>OrderBy</code>、降順のソートが <code>OrderByDescending</code> なので、それぞれ <code>Sort</code>、 <code>SortByDesc</code> というメソッド名でラップしましたが、ここは昇順か降順かをオプション引数で切り替える <code>Sort</code> という名前のメソッドにしても良いでしょう。</p> <p>「昇順か降順かをメソッド名だけで判断できる」という利点はありますが、同じようなことが別の名前のメソッドで定義されているのも、個人的には気持ち悪いと感じます。</p> <p>同様に、<code>ThenBy</code> と <code>ThenByDescending</code> も <code>Then</code> というメソッドでラップしてみます。</p> <pre><code class="csharp">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); </code></pre> <p>オプション引数 <code>byDesc</code> を設定しました。これにより、次のようにコードを書けます。</p> <pre><code class="csharp">// ラップ前 var hoge = GetEmployee.OrderBy(s => s.Age).ThenByDesc(x => x.Salary); // ラップ後 var hoge = GetEmployee.Sort(s => s.Age).Then(s => s.Salary, byDesc: true); </code></pre> <p>もちろん引数名の <code>byDesc</code> を省略することもできますが、理由があり記載しています。理由については、以下の記事で解説しているので、よかったらご覧ください。</p> <p><a href="https://crieit.net/posts/dare-to-write-argument-names-patterns">【C#】あえて引数名を書くパターン</a></p> <h2 id="比較"><a href="#%E6%AF%94%E8%BC%83">比較</a></h2> <p>メソッド名をラップしてみることで、冒頭のコードは次のように書けるようになりました。</p> <pre><code class="csharp">// 変更前 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(); </code></pre> <p>LINQ にあまり慣れていない人でも、よりスッと入ってきやすいコードになったのではないでしょうか。</p> あぱしょに