FLINTERS Engineer's Blog

FLINTERSのエンジニアによる技術ブログ

C#をはじめた頃に知っておきたかった事(Linqメイン)

初めましてkouと言います。
昨年夏から一年ちょいC#にて仕事をしていました。
C#を書いていて、これを最初に教えてくれる人がいたらなぁとか、これを先に知ってたらなぁとか、
そういうった事をLinqメインにざっとまとめてみました。

LinqというはSQL、MXL、Objectに対してSQLライクな文法でアクセスするものです。
今回Linqの説明は省きますが、よくあるfilter,map,なんかをSQLみたいな感じで書けるものです。


多重ループをクエリ式にする
for文のネストが深いと嫌ですよね。
foreachで書かれたものであれば、クエリ式を使う事で簡単に深いネストをさける事ができます。

        public static void TestLinq()
        {
            List<string> testList = new List<string>();
            testList.Add("test");
            testList.Add("sample");
            testList.Add("hoge");
            testList.Add("ex");
            testList.Add("etc");

            foreach (var test in testList)
            {
                foreach (var i in Enumerable.Range(1, 5))
                {
                    System.Console.WriteLine("{0} : {1}", test, i);
                }
            }
        }


foreachの括弧内を抜き出して頭にfromをつけ
最終的に欲しいものをselect文の中に匿名型で書くだけでOKです。
ネストが増えたらその分fromを増すだけです。

        public static void TestLinq2()
        {
            List<string> testList = new List<string>();
            testList.Add("test");
            testList.Add("sample");
            testList.Add("hoge");
            testList.Add("ex");
            testList.Add("etc");

            var resultStrs = from test in testList
                             from i in Enumerable.Range(1, 5)
                             select new { test, i };

            resultStrs.ToList().ForEach(System.Console.WriteLine);
        }


こういう簡単なケースに対応できるのはいいとして、実際には各ループの最中に色々処理があったりすると思います。
こんな感じで。

        public static void TestLinqEx()
        {
            List<string> testList = new List<string>();
            testList.Add("test");
            testList.Add("sample");
            testList.Add("hoge");
            testList.Add("ex");
            testList.Add("etc");

            foreach (var test in testList)
            {
                var addStr = test + " new version";
                foreach (var i in Enumerable.Range(1, 5))
                {
                    var addStr2 = test + ":" + i.ToString();
                    System.Console.WriteLine("{0} : {1}", addStr, addStr2);
                }
            }
        }||<


こういう場合にどうやって対処するかというと、let句を使って対処します。
各ループ内の処理の頭のvarをletに変えて文末の;を取ればOKです。

処理が増える分let句が増えていく感じです。
if,break,continue等入ってないループに関してはこの方法で深いネストを解消できると思います。

今回はForEachメソッドを使わないでforeachで回して出力しています。
匿名型へのアクセスはこんな感じになります。

>|c-sharp|
        public static void TestLinq2()
        {
            List<string> testList = new List<string>();
            testList.Add("test");
            testList.Add("sample");
            testList.Add("hoge");
            testList.Add("ex");
            testList.Add("etc");

            var resultStrs = from test in testList
                             from i in Enumerable.Range(1, 5)
                             select new { test, i };

            resultStrs.ToList().ForEach(System.Console.WriteLine);
        }


便利なオーバーロード
Linqの各種メソッドには便利なオーバーロードがあります。
大抵の場合Select,Whereを使ってから何かする場合のSelect,Whereの部分は省略できます。

Selectを省略できるパターン
以下のコードのpriceListは何かの商品レコードのような物が入っているListだと思ってください。
その商品の中のカテゴリーが3の物の合計値を取得するみたいなプログラムを書いてみました。

        public static void TestSum()
        {
            List<int> priceList = new List<int>();
            priceList.Add(1000);
            priceList.Add(2000);
            priceList.Add(5000);
            priceList.Add(10000);

            var results = from price in priceList
                          from category in Enumerable.Range(1, 5)
                          select new { price, category };

            // selectいらない
            //var sum = results.Where(x => x.category == 3).Select(x => x.price).Sum();
            var sum = results.Where(x => x.category == 3).Sum(x => x.price);
            System.Console.WriteLine(sum);


Whereを省略できるパターン

        public static void TestFirst()
        {
            List<int> priceList = new List<int>();
            priceList.Add(1000);
            priceList.Add(2000);
            priceList.Add(5000);
            priceList.Add(10000);

            var results = from price in priceList
                          from category in Enumerable.Range(1, 5)
                          select new { price, category };

            // whereいらない
            //var firstPrice = results.Where(x => x.category == 3).Select(x => x.price).First().price;
            var firstPrice = results.First(x => x.category == 3).price;
            System.Console.WriteLine(firstPrice);
        }


実際の所はクエリ式の部分のfrom price in priceList.Where(x => x.category == 3)
した方が効率いいのですが、オーバーロードの説明したかったのでこうなってます。

ループのインデックスを取得したい
foreachやクエリ式を使ってるとループのインデックスを取得したくなる事が多いと思います。
foreachのスコープ外にindexとか変数作ってループ内でインクリメントとかしちゃってましたが
Linqではそんな事しないでも簡単にインデックスを取得できます。

        public static void TestIndex()
        {
            List<string> testList = new List<string>();
            testList.Add("test");
            testList.Add("sample");
            testList.Add("hoge");
            testList.Add("ex");
            testList.Add("etc");

            foreach (var test in testList.Select((x, i) => new { x, i }))
                System.Console.WriteLine("{0} : {1}", test.x, test.i);
        }


これでiの部分にインデックスが入ってきます、こんな感じのオーバーロードは各種Linqメソッドにもあるのでとても便利です。

とこんな感じです。 C#とても便利な言語でした。(PHPを眺めながら...)