その他
    ホーム 技術発信 DoRuby 【C#/Unity】LINQを使ってみたらとてもスマートだった
    【C#/Unity】LINQを使ってみたらとてもスマートだった
     

    【C#/Unity】LINQを使ってみたらとてもスマートだった

    この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

    コレクション操作をする際にLINQを使ってみたところ、素晴らしさに気付けたので紹介したいと思います。

    はじめに

    皆様は統合言語クエリ(Language INtegrated Query)すなわちLINQというものをご存知でしょうか?
    私の場合、Unity業務に携わってC#を触り始めて1ヶ月ぐらいで先輩に教えてもらい、ようやく知った概念でした。
    LINQを知らない・よく分からないといった方々にLINQの便利さを知ってもらえるように記事を書いてみました。

    余談ですが、記事を書くにあたりおさらいをするために「LINQ」でググったのですが、福岡県を中心に活動しているアイドルグループが出てきました。
    私はアイドルに詳しくないんですが、全員エンジニアのアイドルグループとかあったら個性強そうですね。というかありそう。

    統合言語クエリって何?

    簡単に言うと、SQLなどで馴染みの深いSELECTやWHEREといった単語を用いてコレクションのデータを操作できる仕組みです。

    C#やVBなど公式にLINQに対応している言語以外にも、JavaやPHPなどメジャーな言語で有志によるLINQ風のライブラリが開発されています。

    LINQを使うとどう変わるのか

    Unityでゲーム(私の場合はソーシャルゲーム)を作っていると、「リストの中から特定の条件に合うものだけを表示する」という処理をすることが多々あります。

    例えば、所持アイテムの中から戦闘中使用可能なアイテムだけを表示したり、全ユニットの中から進化可能なユニットだけを表示したり…。
    一般的にユーザーが持つデータの数が増えれば増えるほど、条件による絞り込み表示の有用性は高くなると思います。

    例として挙げた「所持アイテムの中から戦闘中使用可能なアイテムだけを表示する」処理を実装するときに一番単純明快なのは、以下のコードのようにforeachを使ってリストの要素を片っ端から見ていき、条件に一致するかどうか判断することだと思います。
    (本来C#の命名規則はほとんどPascalCaseのようですが、最も書き慣れているので自分の所属しているプロジェクトの規則に則ることをお許しください。)

    List<Item> usableItemList = new List<Item>();
    
    foreach(var item in itemList){ // itemList = 全所持アイテムのリスト
        if (item.usableInBattle) { // アイテムが戦闘中使用可能かどうかのフラグを見る
            usableItemList.Add(item);
        }
    }
    

    foreachでリストの各要素を見ていき、if文で判定を行うという至ってシンプルな処理です。
    本筋でないので省略しますが、もちろんこの後にusableItemListの中身を表示します。

    このコードをLINQを使って書いてみると、こうなります。

    List<Item> usableItemList = itemList.Where(item => item.usableInBattle);
    

    たった1行で、先ほどの5行の処理を表せます。
    Whereを使うことで、SQLのように条件を指定して絞り込みを行っているわけです。
    5行から1行になったことは視覚的に大きな変化ですが、それ以外にもネストが減ることによる可読性の向上などの恩恵も受けられます。
    ちなみに、LINQを使うためには using System.Linq; する必要があります。

    メソッド構文とクエリ構文

    先ほどの例では、Whereをメソッドのように使用してLINQを記述しました。
    このような記法をメソッド構文と言い、それ以外にLINQにはクエリ構文というものがあります。

    先ほどのコードをクエリ構文を用いて書く場合、

    List<Item> usableItemList = item in itemList
                                where item.usableInBattle;
    

    このようになります。

    どちらを使っても同じ処理が出来るので、最終的には好みに依ると思うのですが、私はC#っぽさ(?)を重視してメソッド構文を使っています。
    そもそも私のように生のSQLを書くことに慣れていない身としては、メソッド構文の方が圧倒的に書きやすく直感的でした。

    LINQの主なメソッド

    LINQには、もちろんWhere以外にもメソッドが用意されています。
    どれも便利なので、私がよく使うものを簡単な例とともに載せておきます。
    ちなみにLINQのメソッドはどれもIEnumerableクラスの拡張メソッドとして定義されています。

    Where 条件指定して要素を抽出する

    List<int> integerList = {0,1,2,3,4,5,6,7,8,9};
    List<int> result = integerList.Where(value => value % 3 == 0); // {0, 3, 6, 9}
    

    Select 要素に対して処理を行い、その結果を返す(射影)

    List<int> integerList = {0,1,2,3,4,5,6,7,8,9};
    List<int> result = integerList.Select(value => value * 10); // {0, 10, 20, 30, 40, 50, 60, 70, 80, 90}
    

    Any 指定した条件を満たす要素が含まれているかどうか判定する

    List<int> integerList = {0,1,2,3,4,5,6,7,8,9};
    List<int> result = integerList.Any(value => value == 0); // true
    List<int> result2 = integerList.Any(value => value > 9); // false
    

    FirstOrDefault 指定した条件を満たす最初の要素を返す 無ければ要素の型の規定値を返す

    List<int> integerList = {0,1,2,3,4,5,6,7,8,9};
    List<int> result = integerList.FirstOrDefault(value => value > 5); // 6
    List<int> result2 = integerList.FirstOrDefault(value => value < 0); // 0
    

    ほぼ同じ処理をするメソッドにFirstがありますが、Firstは要素が見当たらなかった時に例外を発生させます。

    OrderBy 昇順に並べ替えを行う / OrderByDescending 降順に並べ替えを行う

    List<int> integerList = {3,0,2,8,6,9,4,7,1,5};
    List<int> result = integerList.OrderBy(value => value); // {0,1,2,3,4,5,6,7,8,9}
    
    List<Item> itemList = {item1, item2, item3, item4} 
    // item1.price = 100, item2.price = 50, item3.price = 300, item4.price = 0 とする
    
    List<Item> result2 = itemList.OrderByDescending(item => item.price); 
    // {item3, item1, item2, item4}
    

    まとめ

    LINQを使うとコレクションに対する色々な操作が簡単に出来るよ!ということが伝わっていれば幸いです。
    WhereやSelect、OrderByなど、戻り値がIEnumerable型のメソッドはメソッドチェーンして繋げることが出来るので、foreachやifで書くと長ったらしくて複雑な処理でも、LINQなら繋がっているメソッド名を見ることでなんとなく何をしているのかが分かりやすいのも強みだと思います。
    他にも、LINQのメソッドの遅延実行によるメリットなど私もまだまだ理解が足りていない部分もありますが、基本的なメソッドと使い方を覚えるだけでforeachやifによるネストの回数がグッと減ると思います。

    今回も長くなってしまいましたが、お付き合いありがとうございました。