linq ソースで go!
DESCRIPTION
LINQ ソースで GO!. In 名古屋 MS 系秋祭り 2013/09/21 Kouji Matsui (@kekyo2). 自己紹介. けきょ。 会社やってます。 Micoci とまどべん よっかいち。 主に Windows 。 C#, C++/CLI, ATL, C++0x, x86/x64 アセンブラ , WDM, Azure, TFS, OpenCV , Geo, JNI, 鯖管理 , MCP 少々 , 自作 PC, 昔マイコン , 複式簿記経理 最近 は WPF と Prism に足をツッコミ中。. LINQ 知ってますか?. - PowerPoint PPT PresentationTRANSCRIPT
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 は込み入った理由から本当はこの通りではないが、同じように理解してよい
一体、ソースの話はどこにw w 前節までに、 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
完