Edited at

続・はじめての LINQ

More than 1 year has passed since last update.

この記事は前回の「はじめての 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);
}
}
}
}
}


・結合

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);
}
}
}
}


遅延評価

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: #2

Thread started: #3

Thread started: #4

Thread started: #5

Thread started: #6

Thread started: #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