linqソースでgo!

39
LINQソースでGO! IN 名古屋MS系秋祭り 2013/09/21 KOUJI MATSUI (@KEKYO2)

Upload: kouji-matsui

Post on 17-Jul-2015

362 views

Category:

Software


0 download

TRANSCRIPT

Page 1: LINQソースでGO!

LINQソースでGO!

IN 名古屋MS系秋祭り 2013/09/21KOUJI MATSUI (@KEKYO2)

Page 2: LINQソースでGO!

自己紹介

けきょ。

会社やってます。

Micociとまどべんよっかいち。

主にWindows。C#, C++/CLI, ATL, C++0x, x86/x64アセンブラ, WDM, Azure, TFS, OpenCV, Geo, JNI, 鯖管理, MCP少々, 自作PC, 昔マイコン, 複式簿記経理

最近はWPFとPrismに足をツッコミ中。

Page 3: LINQソースでGO!

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で列挙可能

Page 4: LINQソースでGO!

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クラスの配列

配列から抽出する

Page 5: LINQソースでGO!

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を格納

Page 6: LINQソースでGO!

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を格納

Page 7: LINQソースでGO!

「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' の型を明示的に指定してください。

Page 8: LINQソースでGO!

「Where」って何よ? ArrayListクラスのドキュメントを確認。

Whereメソッドが無い。仕方ないか。

Page 9: LINQソースでGO!

「Where」って何よ? そりゃ失礼。では正常なList<T>クラスのドキュメントを確認。

やっぱり無いんですけど ( ゚Д゚)

Page 10: LINQソースでGO!

「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型インスタンスメソッドの呼び出しのように見える(書ける)

Page 11: LINQソースでGO!

「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」の修飾

Page 12: LINQソースでGO!

それで? でも、配列やリストは、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

Page 13: LINQソースでGO!

IEnumerable<T>

T型の配列 T[]

それで?

List<T>

IEnumerable<T> Where<T>(this IEnumerable<T> enumerable, …)

つまり、両方とも抽象基底インターフェイスとして、IEnumerable<T>インターフェイスを実装している。

だから、どちらでも同じWhere拡張メソッドが使

える!!

System.Linq.Enumerableクラス

Page 14: LINQソースでGO!

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」と定義された拡張メソッド。

Page 15: LINQソースでGO!

ところで。 クエリの結果を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で回すことができる条件は?

Page 16: LINQソースでGO!

今さら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

継承・実装関係

Page 17: LINQソースでGO!

配列・リスト・そして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クエリを…

Page 18: LINQソースでGO!

効率は? 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;

クエリの意味が変わってしまわないように注意

Page 19: LINQソースでGO!

起源を思い出せ 効率が変わらないって?

クエリ構文は、メソッド構文に置き換えられてコンパイルされる。

// まとめると、単に連結されただけ。

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

クエリ構文だと、クエリが分割されているだけで効率が悪いように見えるが、実際はそれほどでもない。

Page 20: LINQソースでGO!

結局のところ 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は込み入った理由から本当はこの通りではないが、同じように理解してよい

Page 21: LINQソースでGO!

一体、ソースの話はどこにww 前節までに、LINQクエリの肝は「IEnumerable<T>インターフェイス」と、そ

の拡張メソッド群であることが明らかになりました。というか、これを強くイメージしてほしかったので、長々と解説しました。

言い換えると、「IEnumerable<T>インターフェイスのインスタンスを返しさえすれば、フリーダムにやってOK」って事です。

< マダー?

Page 22: LINQソースでGO!

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;

個数がデカいとちょっと…

Page 23: LINQソースでGO!

オンザフライで乱数を生成(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();}

}

Page 24: LINQソースでGO!

オンザフライで乱数を生成(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メソッドも必要…

Page 25: LINQソースでGO!

オンザフライで乱数を生成(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

Page 26: LINQソースでGO!

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を使うと、コンパイル時に、自動的に前述のような内部クラスが生成される(ステートマシンの生成)

Page 27: LINQソースでGO!

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

}}

可変引数群を受け取る

Page 28: LINQソースでGO!

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)

Page 29: LINQソースでGO!

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

Page 30: LINQソースでGO!

必ず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と変わらないよ?

Page 31: LINQソースでGO!

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

Page 32: LINQソースでGO!

必ずIEnumerable<T>? ParallelQuery<T>クラスは、IEnumerable<T>インターフェイスを実装

している。

じゃあ、Where拡張メソッドの呼び出しは、結局同じってこと??

IEnumerable<T>

ParallelQuery<T>

IEnumerable<T> Where<T>(this IEnumerable<T> enumerable, …)

System.Linq.Enumerableクラス

Page 33: LINQソースでGO!

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を実現する。

Page 34: LINQソースでGO!

わざと似せている 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の実装が使われる。→これによって、クエリのパラレル実行が行われる。

Page 35: LINQソースでGO!

まだある、似て異なる実装 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>

Page 36: LINQソースでGO!

まだある、似て異なる実装 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句を送信するための仕掛けを持った実装。

Page 37: LINQソースでGO!

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

Page 38: LINQソースでGO!

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

Page 39: LINQソースでGO!

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