Upload
kouji-matsui
View
362
Download
0
Embed Size (px)
Citation preview
LINQソースでGO!
IN 名古屋MS系秋祭り 2013/09/21KOUJI MATSUI (@KEKYO2)
自己紹介
けきょ。
会社やってます。
Micociとまどべんよっかいち。
主にWindows。C#, C++/CLI, ATL, C++0x, x86/x64アセンブラ, WDM, Azure, TFS, OpenCV, Geo, JNI, 鯖管理, MCP少々, 自作PC, 昔マイコン, 複式簿記経理
最近はWPFとPrismに足をツッコミ中。
LINQ知ってますか? ビデオチャット製品ではありませんw
アイドルグループではありませんww
.NET Framework 3.5 (C# 3.0)にて導入された、「統合言語クエリ」拡張です。 (Language Integrated Query)
var results =from person in personswhere (person.Age >= 30) && (person.IsFemale == true)orderby person.FirstName, person.LastNameselect person;
foreach (var result in results){
Console.WriteLine(“{0} {1}”, result.FirstName, result.LastName);}
人員一覧の中から、年齢が30歳以上、かつ女性の人員を抽出し、名前・苗字の順でソートする
クエリ結果はforeachで列挙可能
LINQはどんな場面で使える? 配列にクエリを掛ける
var persons = new[]{
new Person { FirstName=“Kouji”, LastName=“Matsui”, Age=41, IsFemale=false },new Person { FirstName=“Mogeko”, LastName=“Moge”, Age=35, IsFemale=true },new Person { FirstName=“Uhyo”, LastName=“Hidebu”, Age=31, IsFemale=true },
};
var results =from person in personswhere (person.Age >= 30) && (person.IsFemale == true)orderby person.FirstName, person.LastNameselect person;
Personクラスの配列
配列から抽出する
LINQはどんな場面で使える? リストにクエリを掛ける
var persons = new List<Person>();persons.Add(
new Person { FirstName=“Kouji”, LastName=“Matsui”, Age=41, IsFemale=false });persons.Add(
new Person { FirstName=“Mogeko”, LastName=“Moge”, Age=35, IsFemale=true });persons.Add(
new Person { FirstName=“Uhyo”, LastName=“Hidebu”, Age=31, IsFemale=true });
var results =from person in personswhere (person.Age >= 30) && (person.IsFemale == true)orderby person.FirstName, person.LastNameselect person;
抽出クエリ文は、配列の時と全く同じ
リストにPersonを格納
LINQはどんな場面で使える? ジェネリックではないリストは駄目
var persons = new ArrayList();persons.Add(
new Person { FirstName=“Kouji”, LastName=“Matsui”, Age=41, IsFemale=false });persons.Add(
new Person { FirstName=“Mogeko”, LastName=“Moge”, Age=35, IsFemale=true });persons.Add(
new Person { FirstName=“Uhyo”, LastName=“Hidebu”, Age=31, IsFemale=true });
// 構文エラー
var result =from person in personswhere (person.Age >= 30) && (person.IsFemale == true)orderby person.FirstName, person.LastNameselect person;
error CS1934: ソース型 'System.Collections.ArrayList' のクエリ パターンの実装が見つかりませんでした。'Where' が見つかりません。範囲変数 'person' の型を明示的に指定してください。
ArrayListにPersonを格納
「Where」って何よ? 「‘Where’ が見つかりません」…
そもそも、その先頭大文字の「Where」って何?
実はLINQのクエリ構文は、メソッド構文に置き換えられてコンパイルされる。
// クエリ構文
var results =from person in personswhere (person.Age >= 30) && (person.IsFemale == true)orderby person.FirstName, person.LastNameselect person;
// メソッド構文(コンパイル時にはこのように解釈される)
var results = persons.Where(person => (person.Age >= 30) && (person.IsFemale == true)).OrderBy(person => person.FirstName).ThenBy(person => person.LastName).Select(person => person);
error CS1934: ソース型 'System.Collections.ArrayList' のクエリ パターンの実装が見つかりませんでした。'Where' が見つかりません。範囲変数 'person' の型を明示的に指定してください。
「Where」って何よ? ArrayListクラスのドキュメントを確認。
Whereメソッドが無い。仕方ないか。
「Where」って何よ? そりゃ失礼。では正常なList<T>クラスのドキュメントを確認。
やっぱり無いんですけど ( ゚Д゚)
「Where」は拡張メソッド 拡張メソッドは、C#3.0にて導入された。
staticクラス内のstaticメソッドの第一引数に「this」を修飾する事で定義できる。
// 拡張メソッドの例。クラス名は完全に任意
public static class SampleExtensions{
// 文字列をintに変換する拡張メソッド
public static int ToInt32(this string stringValue){
return int.Parse(stringValue);}
}
public sealed class MainClass{
public static void Main(){
var string123 = “123”;var int123 = string123.ToInt32(); // 拡張メソッドの呼び出し
}}
string型に対して、「this」の修飾
string型インスタンスメソッドの呼び出しのように見える(書ける)
「Where」は拡張メソッド Whereメソッドは、System.Linq.Enumerableクラスに定義されている。
// System.Linq.Enumerablepublic static class Enumerable{
// Where拡張メソッド(擬似コード)
public static IEnumerable<T> Where<T>(this IEnumerable<T> enumerable,Func<T, bool> predict)
{foreach (var value in enumerable){
if (predict(value) == true){
yield return value;}
}}
}
IEnumerable<T>インターフェイス型に対して「this」の修飾
それで? でも、配列やリストは、IEnumerable<T>型じゃないよ?
.NETの配列は、IEnumerable<T>インターフェイスを自動的に実装している。
// 配列は、IEnumerable<T>を実装している
var persons = new[]{
new Person { FirstName=“Kouji”, LastName=“Matsui” }};
IEnumerable<Person> personsEnumerable = persons; // OK
List<T>クラスは、IEnumerable<T>を実装している。
// List<T>は、IEnumerable<T>を実装している
var persons = new List<Person>();persons.Add(
new Person { FirstName=“Kouji”, LastName=“Matsui” });
IEnumerable<Person> personsEnumerable = persons; // OK
IEnumerable<T>
T型の配列 T[]
それで?
List<T>
IEnumerable<T> Where<T>(this IEnumerable<T> enumerable, …)
つまり、両方とも抽象基底インターフェイスとして、IEnumerable<T>インターフェイスを実装している。
だから、どちらでも同じWhere拡張メソッドが使
える!!
System.Linq.Enumerableクラス
ArrayListは? なぜArrayListクラスは駄目なのか?
ArrayListクラスが実装しているインターフェイスは、IEnumerable<T>ではなく、IEnumerableインターフェイス。
IEnumerableインターフェイスのWhere拡張メソッドは存在しない。
じゃあ、全く使えないかというと、要するにT型を特定して、ジェネリックなIEnumerable<T>に変換すればいい。
// ArraListを用意
var persons = new ArrayList();
// (ArrayListに様々なインスタンスを追加)
// すべての要素をキャスト(キャストに失敗すれば例外がスロー)
IEnumerable<Person> persons2 = persons.Cast<Person>(); // OK
// 又は、指定された型のインスタンスだけを抽出
IEnumerable<Person> persons3 = persons.TypeOf<Person>(); // OK
Cast・TypeOfメソッドは、「this IEnumerable」と定義された拡張メソッド。
ところで。 クエリの結果をforeachで回してたっけ。
var results =from person in personswhere (person.Age >= 30) && (person.IsFemale == true)orderby person.FirstName, person.LastNameselect person;
foreach (var result in results){
Console.WriteLine(“{0} {1}”, result.FirstName, result.LastName);}
ぐるぐる
foreachで回すことができる条件は?
今さらforeach foreachで回すことができるインスタンスは、IEnumerableインターフェイスを
実装していること。
IEnumerableって言うと、配列とか、リストだっけ…
// 配列
var persons = new[]{
new Person { FirstName=“Kouji”, LastName=“Matsui”, Age=41, IsFemale=false },new Person { FirstName=“Mogeko”, LastName=“Moge”, Age=35, IsFemale=true },new Person { FirstName=“Uhyo”, LastName=“Hidebu”, Age=31, IsFemale=true },
};
// 配列を回してみた
foreach (var person in persons){
Console.WriteLine(“{0} {1}”, person.FirstName, person.LastName);}
なんだ、LINQクエリと一緒じゃん。一緒、なのか?? ( ゚Д゚)IEnumerable<T>
T型の配列 T[] List<T>
IEnumerable
継承・実装関係
配列・リスト・そしてLINQクエリ IEnumerable<T>もOKなので、LINQクエリはforeachでそのまま回せる。
「逆に言えば」、LINQクエリはIEnumerable<T>インターフェイスを実装している?
と言う事は?
// 実はLINQクエリの結果はIEnumerable<T>
IEnumerable<Person> results =from person in personswhere (person.Age >= 30) && (person.IsFemale == true)orderby person.FirstName, person.LastNameselect person;
// 前段のLINQクエリに対して、更にLINQクエリを適用する
IEnumerable<Person> results2 =from result in resultswhere result.LastName.StartsWith(“Suzuki”) == trueselect result;
更にこの結果に対してLINQクエリを…
効率は? LINQクエリを数珠つなぎにして、効率悪くないの?
悪いとも言えるし、変わらないともいえる。
where絞り込み条件を完全に統合できるなら、その方が効率が良い。
// where条件をまとめる
var results =from person in personswhere (person.Age >= 30) && (person.IsFemale == true) &&
(result.LastName.StartsWith(“Suzuki”) == true)orderby person.FirstName, person.LastNameselect person;
クエリの意味が変わってしまわないように注意
起源を思い出せ 効率が変わらないって?
クエリ構文は、メソッド構文に置き換えられてコンパイルされる。
// まとめると、単に連結されただけ。
var results2 = persons.Where(person => (person.Age >= 30) && (person.IsFemale == true)).OrderBy(person => person.FirstName).ThenBy(person => person.LastName).Select(person => person). // ← しいて言えばここが無駄
Where(result => result.LastName.StartsWith(“Suzuki”) == true).Select(result => result);
var results = persons.Where(person => (person.Age >= 30) && (person.IsFemale == true)).OrderBy(person => person.FirstName).ThenBy(person => person.LastName).Select(person => person);
var results2 = results.Where(result => result.LastName.StartsWith(“Suzuki”) == true).Select(result => result);
クエリ構文だと、クエリが分割されているだけで効率が悪いように見えるが、実際はそれほどでもない。
結局のところ LINQクエリは、IEnumerable<T>インターフェイスを返すメソッドを数珠つ
なぎにしただけ。
// 全てが、IEnumerable<T>インターフェイスを利用した、拡張メソッド群の呼び出しで解決される。
var results2 = persons.Where(person => (person.Age >= 30) && (person.IsFemale == true)).OrderBy(person => person.FirstName).ThenBy(person => person.LastName).Select(person => person).Where(result => result.LastName.StartsWith(“Suzuki”) == true).Select(result => result);
// System.Linq.Enumerableクラス
public static IEnumerable<T> Where<T>(this IEnumerable<T> enumerable, …);public static IEnumerable<T> Select<T>(this IEnumerable<T> enumerable, …);public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> enumerable, …);public static IEnumerable<T> ThenBy<T>(this IEnumerable<T> enumerable, …);
これらは全て、Enumerableクラスに定義されている拡張メソッド群
OrderByとThenByは込み入った理由から本当はこの通りではないが、同じように理解してよい
一体、ソースの話はどこにww 前節までに、LINQクエリの肝は「IEnumerable<T>インターフェイス」と、そ
の拡張メソッド群であることが明らかになりました。というか、これを強くイメージしてほしかったので、長々と解説しました。
言い換えると、「IEnumerable<T>インターフェイスのインスタンスを返しさえすれば、フリーダムにやってOK」って事です。
< マダー?
IEnumerable<T>を返す LINQで使える独自のメソッドを作りたい。例として、「指定された個数の乱数
を返す」 LINQソースを考える。
// イケてない実装(配列を作って、乱数を格納して返す)
public IEnumerable<int> GetRandomNumbers(int count){
var r = new Random();var results = new int[count];for (var index = 0; index < results.Length; index++){
results[index] = r.Next();}return results; // 配列はIEnumerable<T>を実装しているのでOK
}
// こう使える
var results =from rn in GetRandomNumbers(1000000) // これって…
where (rn % 2) == 0select rn;
個数がデカいとちょっと…
オンザフライで乱数を生成(1) 要するに、IEnumerable<T>で返せばいいのだから、配列やリストでなくても良
い。IEnumerable<T>を実装した、独自のクラスを定義する。
IEnumerable<T>は、IEnumerator<T>のファクトリとなっているので、これらを実装する。
// IEnumerable<int>を実装したクラスを定義
internal sealed class RandomNumberEnumerable : IEnumerable<int>{
private readonly int count_; // 個数を記憶する
public RandomNumberIEnumerable(int count){
count_ = count;}
public IEnumerator<int> GetEnumerator(){
// RandomNumberEnumeratorを作って返す(ファクトリメソッド)
return new RandomNumberEnumerator(count_);}
IEnumerator IEnumerable.GetEnumerator(){
// 非ジェネリック実装は、単にジェネリック実装を呼び出す
return this.GetEnumerator();}
}
オンザフライで乱数を生成(2) GetEnumerator()はIEnumerator<T>を返す必要があるので、そのた
めのクラスを準備。
// 乱数生成の本体クラス
internal sealed class RandomNumberEnumerator : IEnumerator<int>{
private readonly Random r_ = new Random();private readonly int count_;private int remains_;
public RandomNumberEnumerator(int count){
count_ = count;remains_ = count;
}
public int Current{
get;private set;
}
// 次があるかどうかを返す
public bool MoveNext(){
if (remains_ >= 1){
remains_--;this.Current = r.Next(); // 次の値を保持
return true;}return false;
}}
実際には、Resetメソッドも必要…
オンザフライで乱数を生成(3) やっと完成。
// オンザフライ出来た!
public IEnumerable<int> GetRandomNumbers(int count){
// RandomNumberEnumerableクラスを生成
return new RandomNumberEnumerable(count);}
// こう使える
var results =from rn in GetRandomNumbers(1000000) // メモリを過度に消費しない!!
where (rn % 2) == 0select rn;
め、面倒クサ過ぎる orz
yield return C#2.0にて、「yield」予約語が導入された。
これを使うと、IEnumerableインターフェイスの実装が劇的に簡単に!(つまり、前節の方法は、.NET 1.1までの方法)
// イケてる実装
public IEnumerable<int> GetRandomNumbers(int count){
var r = new Random();for (var index = 0; index < count; index++){
yield return r.Next(); // yield returnって書くだけ!!
}}
// こう使える
var results =from rn in GetRandomNumbers(1000000) // 勿論、メモリ消費しない
where (rn % 2) == 0select rn;
yieldを使うと、コンパイル時に、自動的に前述のような内部クラスが生成される(ステートマシンの生成)
yieldを使って、拡張メソッド 任意数のIEnumerable<T>インスタンスを結合する拡張メソッドを作る。
// 適当なstaticクラスに定義
public static IEnumerable<T> Concats<T>(this IEnumerable<T> enumerable,params IEnumerable<T>[] rhss)
{// まず、自分を全て列挙
foreach (var value in enumerable){
yield return value;}
// 可変引数群を列挙
foreach (var rhs in rhss){
// 個々の引数を列挙
foreach (var value in rhs){
yield return value;}
}}
可変引数群を受け取る
yieldを使って、拡張メソッド// 配列
var persons1 = new[]{new Person { FirstName=“Kouji”, LastName=“Matsui”, Age=41, IsFemale=false },new Person { FirstName=“Mogeko”, LastName=“Moge”, Age=35, IsFemale=true },new Person { FirstName=“Uhyo”, LastName=“Hidebu”, Age=31, IsFemale=true },
};
// リスト
var persons2 = new List<Person>();persons2.Add(new Person { FirstName=“Kouji”, LastName=“Matsui”, Age=41, IsFemale=false });persons2.Add(new Person { FirstName=“Mogeko”, LastName=“Moge”, Age=35, IsFemale=true });persons2.Add(new Person { FirstName=“Uhyo”, LastName=“Hidebu”, Age=31, IsFemale=true });
// 何らかのLINQクエリ
var persons3 =from person in personsXwhere (person.Age >= 30) && (person.IsFemale == true)select person;
// 全部結合
var results = persons1.Concats(persons2, persons3);
この部分が可変引数群(rhss)
yieldでこんな事も可能 yieldによって勝手にステートマシンが作られるので、逆手にとって…
// 移動角度群を返すLINQソース
public static IEnumerable<double> EnemyAngles(){
// 敵の移動角度を以下のシーケンスで返す
yield return 0.0;yield return 32.0;yield return 248.0;yield return 125.0;yield return 66.0;yield return 321.0;
// 10ステップはランダムな方角に移動
var r = new Random();for (var index = 0; index < 10; index++){
yield return r.Next(360);}
// 最後に少し動いて死亡
yield return 37.0;yield return 164.0;
}
foreachで回せば、これらの順で値が取得出来る。もちろん、LINQクエリで値を加工することも可能
重要なのは、yieldを使う事で、返却する値を自由自在にコントロールできると言う事単独で値を返したり、ループさせたり、それらを組み合わせたりもOK
必ずIEnumerable<T>? LINQソースとなるためには、必ずIEnumerable<T>を返さなければならな
いのか?
// 例えば、パラレルLINQクエリ
var results =from person in persons.AsParallel()where (person.Age >= 30) && (person.IsFemale == true)orderby person.FirstName, person.LastNameselect person;
// メソッド構文
var results = persons.AsParallel().Where(person => (person.Age >= 30) && (person.IsFemale == true)).OrderBy(person => person.FirstName).ThenBy(person => person.LastName).Select(person => person);
AsParallelするだけで、あとは普通のLINQと変わらないよ?
必ずIEnumerable<T>? パラレルLINQクエリの結果は、実はParallelQuery<T>型。
// 似ているようで、違うのか?
ParallelQuery<T> results = persons.AsParallel().Where(person => (person.Age >= 30) && (person.IsFemale == true)).OrderBy(person => person.FirstName).ThenBy(person => person.LastName).Select(person => person);
必ずIEnumerable<T>? ParallelQuery<T>クラスは、IEnumerable<T>インターフェイスを実装
している。
じゃあ、Where拡張メソッドの呼び出しは、結局同じってこと??
IEnumerable<T>
ParallelQuery<T>
IEnumerable<T> Where<T>(this IEnumerable<T> enumerable, …)
System.Linq.Enumerableクラス
ParallelEnumerableクラス ParallelQuery<T>クラスに対応するWhere拡張メソッドは、
Enumerableクラスではなく、ParallelEnumerableクラスに定義されている。
C#コンパイラは、型がより一致する拡張メソッドを自動的に選択するため、ParallelQuery<T>に対してWhereを呼び出すと、ParallelEnumerable.Whereが呼び出される。
IEnumerable<T> Where<T>(this IEnumerable<T> enumerable, …)
ParallelQuery<T> Where<T>(this ParallelQuery<T> enumerable, …)
IEnumerable<T>
ParallelQuery<T>
System.Linq.Enumerableクラス
System.Linq.ParallelEnumerableクラス
ParallelQuery<T>の場合は、こっちのWhereが呼び出される。この実装がパラレルLINQを実現する。
わざと似せている ParallelEnumerableクラスには、Enumerableクラスに定義されているメソッ
ドと同じシグネチャ(但し、IEnumerable<T> → ParallelQuery<T>)の、全く異なる実装が定義されている。
// System.Linq.ParallelEnumerableクラス
public static ParallelQuery<T> AsParallel<T>(this IEnumerable<T> enumerable);public static ParallelQuery<T> Where<T>(this ParallelQuery<T> enumerable, …);public static ParallelQuery<T> Select<T>(this ParallelQuery<T> enumerable, …);public static ParallelQuery<T> OrderBy<T>(this ParallelQuery<T> enumerable, …);public static ParallelQuery<T> ThenBy<T>(this ParallelQuery<T> enumerable, …);
戻り値の型も、ParallelQuery<T>となっているので、
// メソッドの連結
ParallelQuery<T> results = persons.AsParallel().Where(person => (person.Age >= 30) && (person.IsFemale == true)).OrderBy(person => person.FirstName).ThenBy(person => person.LastName).Select(person => person;
これらの戻り値の型は、すべからくParallelQuery<T>型。だから、全てParallelEnumerableの実装が使われる。→これによって、クエリのパラレル実行が行われる。
まだある、似て異なる実装 LINQ to SQLやLINQ to EntitiesのデータベースコンテキストからLINQク
エリを記述すると、IEnumerable<T>ではなく、IQueryable<T>が返される。
// LINQ to Entitiesに対して、LINQクエリを記述する
var results =from person in personsContext // DBコンテキストがソース
where (person.Age >= 30) && (person.IsFemale == true)orderby person.FirstName, person.LastNameselect person;
// メソッド構文と戻り値の型
IQueryable<Person> results = personsContext.Where(person => (person.Age >= 30) && (person.IsFemale == true)).OrderBy(person => person.FirstName).ThenBy(person => person.LastName).Select(person => person);
IQueryable<T>
まだある、似て異なる実装 IQueryable<T>に対応するWhere拡張メソッドは、Enumerableクラス
ではなく、Queryableクラスに定義されている。
考え方はパラレルLINQと同じ。
IEnumerable<T> Where<T>(this IEnumerable<T> enumerable, …)
IQueryable<T> Where<T>(this IQueryable<T> queryable, …)
IEnumerable<T>
IQueryable<T>
System.Linq.Enumerableクラス
System.Linq.Queryableクラス
データベースにWHERE句を送信するための仕掛けを持った実装。
やっぱりLINQソースは IEnumerable<T>を継承したクラスやインターフェイスを使うのか?
ReactiveExtensionライブラリが最後の常識を覆す。
// マウス移動イベント発生時に座標をフィルタする
IObservable<Point> rx =Observable.FromEvent<MouseEventArgs>(window, “MouseMove”).Select(ev => ev.EventArgs.GetPosition(window)).Where(pos => (pos.X < 100) && (pos.Y < 100));
// rxの条件が満たされたときに実行する内容を記述
rx.Subscribe(pos =>{
window.Text = string.Format(“{0}, {1}”, pos.X, pos.Y);});
IObservable<T>は、IEnumerable<T>と全く関係がない。しかし、WhereやSelect拡張メソッドを用意する事で、まるでLINQクエリのように見えるようにしている。
まとめ ベーシックなLINQクエリは、すべからくIEnumerable<T>を使用する。その場合、Enumerableクラスに定義された拡張メソッドを使用して、LINQの機能を実現している。
独自の拡張メソッドを定義すれば、LINQ演算子を追加できる。
独自のLINQソースを作るなら、yield構文を使うと良い。
拡張されたLINQクエリ(パラレルLINQやLINQ to Entitiesなど)は、IEnumerable<T>を継承した、新たなクラスやインターフェイスを使用して、拡張メソッドを切り替えさせる事で、似て異なる動作を実現する。これにより、既存の拡張メソッドの動作に影響を与えることなく、かつ、容易に理解可能なAPIを生み出すことができる。
IEnumerable<T>と全く関係のない型を使用したとしても、まるでLINQクエリのように見せることができる。これを「Fluent API」パターンと呼ぶ。Fluent APIを用意すれば、LINQクエリのようなフレンドリ感と、VS上でのサクサクタイピングが実現する。
ご静聴ありがとうございました m(_ _)m