1 / 39

LINQ ソースで GO!

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 知ってますか?.

Download Presentation

LINQ ソースで GO!

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. LINQソースでGO! In 名古屋MS系秋祭り 2013/09/21 Kouji Matsui (@kekyo2)

  2. 自己紹介 • けきょ。 • 会社やってます。 • Micociとまどべんよっかいち。 • 主にWindows。C#, C++/CLI, ATL, C++0x, x86/x64アセンブラ, WDM, Azure, TFS, OpenCV, Geo, JNI, 鯖管理, MCP少々, 自作PC, 昔マイコン, 複式簿記経理 • 最近はWPFとPrismに足をツッコミ中。

  3. LINQ知ってますか? • ビデオチャット製品ではありませんw • アイドルグループではありませんww • .NET Framework 3.5 (C# 3.0)にて導入された、「統合言語クエリ」拡張です。 (Language Integrated Query) 人員一覧の中から、年齢が30歳以上、かつ女性の人員を抽出し、名前・苗字の順でソートする var results = from person in persons where (person.Age >= 30) && (person.IsFemale == true) orderbyperson.FirstName, person.LastName select person; foreach(varresult in results){ Console.WriteLine(“{0} {1}”, result.FirstName, result.LastName); } クエリ結果はforeachで列挙可能

  4. LINQはどんな場面で使える? • 配列にクエリを掛ける Personクラスの配列 varpersons = 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 persons where (person.Age >= 30) && (person.IsFemale == true) orderbyperson.FirstName, person.LastName select person; 配列から抽出する

  5. LINQはどんな場面で使える? • リストにクエリを掛ける リストにPersonを格納 varpersons = 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 persons where (person.Age >= 30) && (person.IsFemale == true) orderbyperson.FirstName, person.LastName select person; 抽出クエリ文は、配列の時と全く同じ

  6. LINQはどんな場面で使える? • ジェネリックではないリストは駄目 ArrayListにPersonを格納 varpersons = newArrayList(); 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 inpersons where (person.Age >= 30) && (person.IsFemale == true) orderbyperson.FirstName, person.LastName select person; error CS1934: ソース型 'System.Collections.ArrayList' のクエリ パターンの実装が見つかりませんでした。'Where' が見つかりません。範囲変数 'person' の型を明示的に指定してください。

  7. 「Where」って何よ? • 「‘Where’ が見つかりません」… • そもそも、その先頭大文字の「Where」って何? • 実はLINQのクエリ構文は、メソッド構文に置き換えられてコンパイルされる。 // クエリ構文 var results = from person in persons where (person.Age >= 30) && (person.IsFemale == true) orderbyperson.FirstName, person.LastName select 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' の型を明示的に指定してください。

  8. 「Where」って何よ? • ArrayListクラスのドキュメントを確認。 Whereメソッドが無い。仕方ないか。

  9. 「Where」って何よ? • そりゃ失礼。では正常なList<T>クラスのドキュメントを確認。 やっぱり無いんですけど ( ゚Д゚)

  10. 「Where」は拡張メソッド • 拡張メソッドは、C#3.0にて導入された。 • staticクラス内のstaticメソッドの第一引数に「this」を修飾する事で定義できる。 string型に対して、「this」の修飾 // 拡張メソッドの例。クラス名は完全に任意 public static class SampleExtensions { // 文字列をintに変換する拡張メソッド public staticint ToInt32(this string stringValue) { return int.Parse(stringValue); } } public sealed class MainClass { public static void Main() { var string123 = “123”; var int123 = string123.ToInt32(); // 拡張メソッドの呼び出し }} string型インスタンスメソッドの呼び出しのように見える(書ける)

  11. 「Where」は拡張メソッド • Whereメソッドは、System.Linq.Enumerableクラスに定義されている。 // System.Linq.Enumerable public static class Enumerable { // Where拡張メソッド(擬似コード) public static IEnumerable<T> Where<T>( this IEnumerable<T> enumerable, Func<T, bool> predict) { foreach(varvalue in enumerable) { if (predict(value) == true) { yield return value; } } } } IEnumerable<T>インターフェイス型に対して「this」の修飾

  12. それで? • でも、配列やリストは、IEnumerable<T>型じゃないよ? • .NETの配列は、IEnumerable<T>インターフェイスを自動的に実装している。 // 配列は、IEnumerable<T>を実装している varpersons = new[] { new Person { FirstName=“Kouji”, LastName=“Matsui” } }; IEnumerable<Person>personsEnumerable = persons; // OK • List<T>クラスは、IEnumerable<T>を実装している。 // List<T>は、IEnumerable<T>を実装している varpersons = new List<Person>(); persons.Add( new Person { FirstName=“Kouji”, LastName=“Matsui” }); IEnumerable<Person>personsEnumerable = persons; // OK

  13. それで? • つまり、両方とも抽象基底インターフェイスとして、IEnumerable<T>インターフェイスを実装している。 System.Linq.Enumerableクラス IEnumerable<T> IEnumerable<T> Where<T>(this IEnumerable<T> enumerable, …) だから、どちらでも同じ Where拡張メソッドが使える!! T型の配列 T[] List<T>

  14. ArrayListは? • なぜArrayListクラスは駄目なのか? • ArrayListクラスが実装しているインターフェイスは、IEnumerable<T>ではなく、IEnumerableインターフェイス。 • IEnumerableインターフェイスのWhere拡張メソッドは存在しない。 • じゃあ、全く使えないかというと、要するにT型を特定して、ジェネリックなIEnumerable<T>に変換すればいい。 // ArraListを用意 varpersons = newArrayList(); // (ArrayListに様々なインスタンスを追加) // すべての要素をキャスト(キャストに失敗すれば例外がスロー) IEnumerable<Person>persons2 = persons.Cast<Person>(); // OK // 又は、指定された型のインスタンスだけを抽出 IEnumerable<Person>persons3 = persons.TypeOf<Person>(); // OK Cast・TypeOfメソッドは、 「this IEnumerable」と定義された拡張メソッド。

  15. ところで。 • クエリの結果をforeachで回してたっけ。 varresults = from person in persons where (person.Age >= 30) && (person.IsFemale == true) orderbyperson.FirstName, person.LastName select person; foreach(varresult inresults){ Console.WriteLine(“{0} {1}”, result.FirstName, result.LastName); } ぐるぐる • foreachで回すことができる条件は?

  16. 今さらforeach • foreachで回すことができるインスタンスは、IEnumerableインターフェイスを実装していること。 • IEnumerableって言うと、配列とか、リストだっけ… // 配列 varpersons = 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(varperson inpersons){ Console.WriteLine(“{0} {1}”, person.FirstName, person.LastName); } 継承・実装関係 IEnumerable IEnumerable<T> なんだ、LINQクエリと一緒じゃん。 一緒、なのか?? ( ゚Д゚) T型の配列 T[] List<T>

  17. 配列・リスト・そしてLINQクエリ • IEnumerable<T>もOKなので、LINQクエリはforeachでそのまま回せる。 • 「逆に言えば」、LINQクエリはIEnumerable<T>インターフェイスを実装している? • と言う事は? // 実はLINQクエリの結果はIEnumerable<T> IEnumerable<Person> results = from person in persons where (person.Age >= 30) && (person.IsFemale == true) orderbyperson.FirstName, person.LastName select person; // 前段のLINQクエリに対して、更にLINQクエリを適用する IEnumerable<Person> results2 = from result inresults whereresult.LastName.StartsWith(“Suzuki”) == true select result; 更にこの結果に対してLINQクエリを…

  18. 効率は? • LINQクエリを数珠つなぎにして、効率悪くないの? • 悪いとも言えるし、変わらないともいえる。 • where絞り込み条件を完全に統合できるなら、その方が効率が良い。 クエリの意味が変わってしまわないように注意 // where条件をまとめる var results = from person in persons where (person.Age >= 30) && (person.IsFemale == true) && (result.LastName.StartsWith(“Suzuki”) == true) orderbyperson.FirstName, person.LastName select person;

  19. 起源を思い出せ • 効率が変わらないって? • クエリ構文は、メソッド構文に置き換えられてコンパイルされる。 varresults = persons. Where(person => (person.Age >= 30) && (person.IsFemale == true)). OrderBy(person => person.FirstName). ThenBy(person => person.LastName). Select(person => person); varresults2 = results. Where(result => result.LastName.StartsWith(“Suzuki”) == true). Select(result => result); クエリ構文だと、クエリが分割されているだけで効率が悪いように見えるが、実際はそれほどでもない。 // まとめると、単に連結されただけ。 varresults2= 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);

  20. 結局のところ • LINQクエリは、IEnumerable<T>インターフェイスを返すメソッドを数珠つなぎにしただけ。 これらは全て、Enumerableクラスに定義されている拡張メソッド群 // 全てが、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>(thisIEnumerable<T> enumerable, …); public static IEnumerable<T> Select<T>(thisIEnumerable<T> enumerable, …); public static IEnumerable<T> OrderBy<T>(thisIEnumerable<T> enumerable, …); public static IEnumerable<T> ThenBy<T>(thisIEnumerable<T> enumerable, …); OrderByとThenByは込み入った理由から本当はこの通りではないが、同じように理解してよい

  21. 一体、ソースの話はどこにww • 前節までに、LINQクエリの肝は「IEnumerable<T>インターフェイス」と、その拡張メソッド群であることが明らかになりました。というか、これを強くイメージしてほしかったので、長々と解説しました。 • 言い換えると、「IEnumerable<T>インターフェイスのインスタンスを返しさえすれば、フリーダムにやってOK」って事です。 < マダー?

  22. IEnumerable<T>を返す • LINQで使える独自のメソッドを作りたい。例として、「指定された個数の乱数を返す」 LINQソースを考える。 個数がデカいとちょっと… // イケてない実装(配列を作って、乱数を格納して返す) publicIEnumerable<int> GetRandomNumbers(int count) { varr = new Random(); varresults = new int[count]; for (var index = 0; index < results.Length; index++) { results[index] = r.Next(); } return results; // 配列はIEnumerable<T>を実装しているのでOK } // こう使える varresults = fromrninGetRandomNumbers(1000000) // これって… where (rn % 2) == 0 selectrn;

  23. オンザフライで乱数を生成(1) • 要するに、IEnumerable<T>で返せばいいのだから、配列やリストでなくても良い。IEnumerable<T>を実装した、独自のクラスを定義する。 • IEnumerable<T>は、IEnumerator<T>のファクトリとなっているので、これらを実装する。 // IEnumerable<int>を実装したクラスを定義 internal sealed class RandomNumberEnumerable : IEnumerable<int> { private readonlyintcount_; // 個数を記憶する publicRandomNumberIEnumerable(int count) { count_ = count; } publicIEnumerator<int> GetEnumerator() { // RandomNumberEnumeratorを作って返す(ファクトリメソッド) return new RandomNumberEnumerator(count_); } IEnumeratorIEnumerable.GetEnumerator() { // 非ジェネリック実装は、単にジェネリック実装を呼び出す return this.GetEnumerator(); } }

  24. オンザフライで乱数を生成(2) • GetEnumerator()はIEnumerator<T>を返す必要があるので、そのためのクラスを準備。 // 乱数生成の本体クラス internal sealed class RandomNumberEnumerator : IEnumerator<int> { private readonly Random r_ = new Random(); private readonlyintcount_; private intremains_; publicRandomNumberEnumerator(int count) { count_ = count; remains_ = count; } public intCurrent { get; private set; } // 次があるかどうかを返す publicboolMoveNext() { if (remains_ >= 1) { remains_--; this.Current = r.Next(); // 次の値を保持 return true; } return false; } } 実際には、Resetメソッドも必要…

  25. オンザフライで乱数を生成(3) • やっと完成。 // オンザフライ出来た! publicIEnumerable<int> GetRandomNumbers(int count) { // RandomNumberEnumerableクラスを生成 return new RandomNumberEnumerable(count); } // こう使える varresults = fromrninGetRandomNumbers(1000000) // メモリを過度に消費しない!! where (rn % 2) == 0 selectrn; め、面倒クサ過ぎる orz

  26. yield return yieldを使うと、コンパイル時に、自動的に前述のような内部クラスが生成される (ステートマシンの生成) • C#2.0にて、「yield」予約語が導入された。 • これを使うと、IEnumerableインターフェイスの実装が劇的に簡単に!(つまり、前節の方法は、.NET 1.1までの方法) // イケてる実装 publicIEnumerable<int> GetRandomNumbers(int count) { varr = new Random(); for (var index = 0; index < count; index++) { yield return r.Next(); // yield returnって書くだけ!! } } // こう使える varresults = fromrninGetRandomNumbers(1000000) // 勿論、メモリ消費しない where (rn % 2) == 0 selectrn;

  27. yieldを使って、拡張メソッド • 任意数のIEnumerable<T>インスタンスを結合する拡張メソッドを作る。 // 適当なstaticクラスに定義 publicstaticIEnumerable<T> Concats<T>( this IEnumerable<T> enumerable, paramsIEnumerable<T>[] rhss) { // まず、自分を全て列挙 foreach(varvalue in enumerable) { yieldreturn value; } // 可変引数群を列挙 foreach(varrhsinrhss) { // 個々の引数を列挙foreach(varvalue inrhs) { yieldreturn value; } } } 可変引数群を受け取る

  28. yieldを使って、拡張メソッド // 配列 varpersons1 = 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 }, }; // リスト varpersons2 = new List<Person>(); persons2.Add(newPerson { FirstName=“Kouji”, LastName=“Matsui”, Age=41, IsFemale=false }); persons2.Add(newPerson { FirstName=“Mogeko”, LastName=“Moge”, Age=35, IsFemale=true }); persons2.Add(newPerson { FirstName=“Uhyo”, LastName=“Hidebu”, Age=31, IsFemale=true }); // 何らかのLINQクエリ varpersons3 = from person inpersonsX where (person.Age >= 30) && (person.IsFemale == true) select person; // 全部結合 varresults = persons1.Concats(persons2, persons3); この部分が可変引数群(rhss)

  29. yieldでこんな事も可能 • yieldによって勝手にステートマシンが作られるので、逆手にとって… // 移動角度群を返すLINQソース publicstaticIEnumerable<double> EnemyAngles() { // 敵の移動角度を以下のシーケンスで返す yieldreturn0.0; yieldreturn32.0; yieldreturn248.0; yieldreturn125.0; yieldreturn66.0; yieldreturn321.0; // 10ステップはランダムな方角に移動 varr = new Random(); for (varindex = 0; index < 10; index++) { yieldreturnr.Next(360); } // 最後に少し動いて死亡 yieldreturn37.0; yieldreturn164.0; } foreachで回せば、これらの順で値が取得出来る。 もちろん、LINQクエリで値を加工することも可能 重要なのは、yieldを使う事で、返却する値を自由自在にコントロールできると言う事 単独で値を返したり、ループさせたり、それらを組み合わせたりもOK

  30. 必ずIEnumerable<T>? • LINQソースとなるためには、必ずIEnumerable<T>を返さなければならないのか? // 例えば、パラレルLINQクエリ var results = from person inpersons.AsParallel() where (person.Age >= 30) && (person.IsFemale == true) orderbyperson.FirstName, person.LastName select 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と変わらないよ?

  31. 必ず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);

  32. 必ずIEnumerable<T>? • ParallelQuery<T>クラスは、IEnumerable<T>インターフェイスを実装している。 • じゃあ、Where拡張メソッドの呼び出しは、結局同じってこと?? System.Linq.Enumerableクラス IEnumerable<T> IEnumerable<T> Where<T>(this IEnumerable<T> enumerable, …) ParallelQuery<T>

  33. ParallelEnumerableクラス • ParallelQuery<T>クラスに対応するWhere拡張メソッドは、Enumerableクラスではなく、ParallelEnumerableクラスに定義されている。 • C#コンパイラは、型がより一致する拡張メソッドを自動的に選択するため、ParallelQuery<T>に対してWhereを呼び出すと、ParallelEnumerable.Whereが呼び出される。 System.Linq.Enumerableクラス IEnumerable<T> IEnumerable<T> Where<T>(this IEnumerable<T> enumerable, …) ParallelQuery<T>の場合は、こっちのWhereが呼び出される。この実装がパラレルLINQを実現する。 System.Linq.ParallelEnumerableクラス ParallelQuery<T> ParallelQuery<T> Where<T>(this ParallelQuery<T>enumerable, …)

  34. わざと似せている • ParallelEnumerableクラスには、Enumerableクラスに定義されているメソッドと同じシグネチャ(但し、IEnumerable<T> → ParallelQuery<T>)の、全く異なる実装が定義されている。 戻り値の型も、ParallelQuery<T>となっているので、 // System.Linq.ParallelEnumerableクラス public static ParallelQuery<T> AsParallel<T>(thisIEnumerable<T> enumerable); public static ParallelQuery<T> Where<T>(thisParallelQuery<T> enumerable, …); public static ParallelQuery<T> Select<T>(thisParallelQuery<T> enumerable, …); public static ParallelQuery<T> OrderBy<T>(thisParallelQuery<T> enumerable, …); public static ParallelQuery<T> ThenBy<T>(thisParallelQuery<T> enumerable, …); // メソッドの連結 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の実装が使われる。 →これによって、クエリのパラレル実行が行われる。

  35. まだある、似て異なる実装 • LINQto SQLやLINQ to EntitiesのデータベースコンテキストからLINQクエリを記述すると、IEnumerable<T>ではなく、IQueryable<T>が返される。 // LINQ to Entitiesに対して、LINQクエリを記述する var results = from person inpersonsContext// DBコンテキストがソース where (person.Age >= 30) && (person.IsFemale == true) orderbyperson.FirstName, person.LastName select person; IQueryable<T> // メソッド構文と戻り値の型 IQueryable<Person> results = personsContext. Where(person => (person.Age >= 30) && (person.IsFemale == true)). OrderBy(person => person.FirstName). ThenBy(person => person.LastName). Select(person => person);

  36. まだある、似て異なる実装 • IQueryable<T>に対応するWhere拡張メソッドは、Enumerableクラスではなく、Queryableクラスに定義されている。 • 考え方はパラレルLINQと同じ。 System.Linq.Enumerableクラス IEnumerable<T> IEnumerable<T> Where<T>(this IEnumerable<T> enumerable, …) System.Linq.Queryableクラス IQueryable<T> IQueryable<T> Where<T>(this IQueryable<T>queryable, …) データベースにWHERE句を送信するための仕掛けを持った実装。

  37. やっぱり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クエリのように見えるようにしている。

  38. まとめ • ベーシックな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上でのサクサクタイピングが実現する。

  39. ご静聴ありがとうございました m(_ _)m

More Related