LoginSignup
64
81

More than 3 years have passed since last update.

続・はじめての LINQ

Last updated at Posted at 2017-03-28

この記事は前回の「はじめての LINQ」の補足的な内容になっています。

まだ前回の記事を読んでいない人は、先にそちらから読むことをおすすめします。

LINQ の使い方

前回の「はじめての LINQ」では紹介しきれなかったメソッドをいくつか見ていきましょう。

・射影

using System;
using System.Linq;

namespace LinqTest
{
    class MainClass
    {
        public static void Main(string[] args)
        {
            // Fruit クラスは定義せず、匿名クラスを使用する
            var fruitList = new[] {
                new  {Name = "りんご", Price = 300},
                new  {Name = "バナナ", Price = 300},
                new  {Name = "パイナップル", Price = 1000},
                new  {Name = "いちご", Price = 500},
            };

            // 各要素を射影する
            var selectList = fruitList.Select(x => x.Name);
            Console.WriteLine("=== selectList ===");
            foreach (var x in selectList)
            {
                Console.WriteLine(x);
            }

            // 各要素を複数の要素に平坦化して射影する
            var selectManyList = fruitList.SelectMany(x => x.Name.ToCharArray());
            Console.WriteLine("=== selectManyList ===");
            foreach (var x in selectManyList)
            {
                Console.WriteLine(x);
            }

            // 各要素をグループ化する
            var groupByList = fruitList.GroupBy(x => x.Price);
            Console.WriteLine("=== groupByList ===");
            foreach (var g in groupByList)
            {
                Console.WriteLine("Key: " + g.Key);
                foreach (var x in g)
                {
                    Console.WriteLine(" " + x);
                }
            }
        }
    }
}

[コンソール出力結果]

=== selectList ===
りんご
バナナ
パイナップル
いちご
=== selectManyList ===
り
ん
ご
バ
ナ
ナ
パ
イ
ナ
ッ
プ
ル
い
ち
ご
=== groupByList ===
Key: 300
 { Name = りんご, Price = 300 }
 { Name = バナナ, Price = 300 }
Key: 1000
 { Name = パイナップル, Price = 1000 }
Key: 500
 { Name = いちご, Price = 500 }

・結合

using System;
using System.Linq;

namespace LinqTest
{
    class MainClass
    {
        public static void Main(string[] args)
        {
            // Fruit クラスは定義せず、匿名クラスを使用する
            var fruitList1 = new[] {
                new  {Name = "りんご", Price = 300},
                new  {Name = "バナナ", Price = 300},
                new  {Name = "パイナップル", Price = 1000},
                new  {Name = "いちご", Price = 500},
            };

            var fruitList2 = new[] {
                new  {Name = "りんご", Stock = 100},
                new  {Name = "バナナ", Stock = 50},
                new  {Name = "パイナップル", Stock = 40},
            };

            var fruitList3 = new[] {
                new  {Name = "ぶどう", Price = 700},
                new  {Name = "もも", Price = 600},
            };

            // 内部結合
            var joinList = fruitList1.Join(
                fruitList2,
                outer => outer.Name,
                inner => inner.Name,
                (outer, inner) => new {
                    outer.Name,
                    outer.Price,
                    inner.Stock
                });
            Console.WriteLine("=== joinList ===");
            foreach (var x in joinList)
            {
                Console.WriteLine(x);
            }

            // 外部結合
            var groupJoinList = fruitList1.GroupJoin(
                fruitList2,
                outer => outer.Name,
                inner => inner.Name,
                (outer, inner) => new {
                    outer.Name,
                    outer.Price,
                    Stock = inner.Select(x => x.Stock).Sum()
                });
            Console.WriteLine("=== groupJoinList ===");
            foreach (var x in groupJoinList)
            {
                Console.WriteLine(x);
            }

            // 連結
            var concatList = fruitList1.Concat(fruitList3);
            Console.WriteLine("=== concatList ===");
            foreach (var x in concatList)
            {
                Console.WriteLine(x);
            }
        }
    }
}

[コンソール出力結果]

=== joinList ===
{ Name = りんご, Price = 300, Stock = 100 }
{ Name = バナナ, Price = 300, Stock = 50 }
{ Name = パイナップル, Price = 1000, Stock = 40 }
=== groupJoinList ===
{ Name = りんご, Price = 300, Stock = 100 }
{ Name = バナナ, Price = 300, Stock = 50 }
{ Name = パイナップル, Price = 1000, Stock = 40 }
{ Name = いちご, Price = 500, Stock = 0 }
=== concatList ===
{ Name = りんご, Price = 300 }
{ Name = バナナ, Price = 300 }
{ Name = パイナップル, Price = 1000 }
{ Name = いちご, Price = 500 }
{ Name = ぶどう, Price = 700 }
{ Name = もも, Price = 600 }

遅延評価

LINQ は遅延評価されます。
と、いきなりそう言われてもよく分からないと思いますので、まずは以下のコードを実行してみてください。
また、参考までに私の実行環境でのコンソール出力結果も載せておきます。

using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;

namespace LinqTest
{
    class MainClass
    {
        public static void Main(string[] args)
        {
            // LINQ 処理
            var stopWatch1 = new Stopwatch();
            stopWatch1.Start();
            var list = Enumerable.Range(1, 100);
            var resultList = list
                             .Select(x => HeavyFunc(x));
            stopWatch1.Stop();
            Console.WriteLine("stopWatch1 TotalSeconds: " + stopWatch1.Elapsed.TotalSeconds);

            // コンソール出力処理
            var stopWatch2 = new Stopwatch();
            stopWatch2.Start();
            foreach (var x in resultList)
            {
                Console.Write(x + " ");
            }
            Console.WriteLine();
            stopWatch2.Stop();
            Console.WriteLine("stopWatch2 TotalSeconds: " + stopWatch2.Elapsed.TotalSeconds);

            Console.WriteLine();
            Console.ReadLine();
        }

        static int HeavyFunc(int number)
        {
            Thread.Sleep(100); // 時間のかかる処理を想定
            return number;
        }
    }
}

[コンソール出力結果]

stopWatch1 TotalSeconds: 0.0027506
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 
stopWatch2 TotalSeconds: 10.2689368

このコードでは 「LINQ 処理」と「コンソール出力処理」の 2 つのブロックに分けて、それぞれの処理に掛かった時間を計測しています。

「LINQ 処理」では、処理の重い HeavyFunc メソッドを 100 回実行しています。
また、「コンソール出力処理」では、その「LINQ 処理」の結果をコンソールに出力しているだけです。

ここで注目してほしいのは、処理が重いはずの「LINQ 処理」が僅か 0.002 秒しか掛かっていないのに、単純な処理しかしていないはずの「コンソール出力処理」には 10 秒も掛かっています。

なぜこのような結果になっているかというと LINQ は結果が必要になるまで処理を保留しているからです。
LINQ は、このようにして無駄な処理は行わないようになっています。

この処理が最適化されるということは遅延評価の大きなメリットです。
しかし、慣れていないと、いつ処理が実行されているのか分かりづらく、デバッグがやりづらいというデメリットもあります。

PLINQ

LINQ を並列実行できる PLINQ という機能があります。

使い方は LINQ のメソッドチェーンの中に AsParallel メソッドを追加するだけです。
簡単ですね。

using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;

namespace LinqTest
{
    class MainClass
    {
        public static void Main(string[] args)
        {
            // LINQ 処理
            var stopWatch1 = new Stopwatch();
            stopWatch1.Start();
            var list = Enumerable.Range(1, 100);
            var resultList = list
                             .AsParallel() // 並列実行させる
                             .Select(x => HeavyFunc(x));
            stopWatch1.Stop();
            Console.WriteLine("stopWatch1 TotalSeconds: " + stopWatch1.Elapsed.TotalSeconds);

            // コンソール出力処理
            var stopWatch2 = new Stopwatch();
            stopWatch2.Start();
            foreach (var x in resultList)
            {
                Console.Write(x + " ");
            }
            Console.WriteLine();
            stopWatch2.Stop();
            Console.WriteLine("stopWatch2 TotalSeconds: " + stopWatch2.Elapsed.TotalSeconds);

            Console.WriteLine();
            Console.ReadLine();
        }

        static int HeavyFunc(int number)
        {
            Thread.Sleep(100); // 時間のかかる処理を想定
            return number;
        }
    }
}

[コンソール出力結果]

stopWatch1 TotalSeconds: 0.0048343
Thread started: <Thread Pool> #2
Thread started: <Thread Pool> #3
Thread started: <Thread Pool> #4
Thread started: <Thread Pool> #5
Thread started: <Thread Pool> #6
Thread started: <Thread Pool> #7
1 4 2 7 5 8 11 9 12 16 13 15 18 17 19 22 21 23 28 25 26 30 29 31 39 37 33 40 38 34 45 47 41 46 48 42 55 53 49 56 54 50 63 61 59 64 62 60 69 71 67 70 72 68 77 79 75 78 80 76 87 85 81 88 86 82 93 95 89 94 96 90 3 6 10 14 20 24 27 32 35 36 43 44 51 52 57 58 65 66 73 74 83 84 91 92 97 98 99 100 
stopWatch2 TotalSeconds: 2.8799779

このコードは、先ほどの遅延評価のサンプルコードを並列化したものですが、処理に掛かった時間は先ほどの 3 分の 1 以下になっています。

ただし、並列化したからといって、いつも速くなるとは限りませんので注意してください。

いろいろな LINQ

今まで紹介してきた配列やコレクションに対して処理を行う LINQ は「LINQ to Objects」と呼ばれています。

それ以外にもいろいろなデータソースに対して処理行う LINQ ライブラリがあります。

LINQ to Objects
LINQ to XML
LINQ to JSON
LINQ to DataSet
LINQ to SQL
LINQ to Entities
LINQ to GameObject
LINQ to BigQuery

RX(Reactive Extensions)

RX(Reactive Extensions)は一言で言うと LINQ に「非同期」と「イベント」の要素を加えて拡張したライブラリです。

この記事では詳しく紹介しませんので、以下のリンクを参考にしてください。

@IT>連載:Reactive Extensions(Rx)入門
http://www.atmarkit.co.jp/fdotnet/introrx/index/index.html

かずきのBlog@hatena>Reactive Extensionsのはじめかた
http://blog.okazuki.jp/entry/2015/03/23/203825

xin9le.net>Rx入門
http://blog.xin9le.net/entry/rx-intro

64
81
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
64
81