An Internal of LINQ to Objects

33,319 views
33,348 views

Published on

LINQ Study #3

0 Comments
36 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
33,319
On SlideShare
0
From Embeds
0
Number of Embeds
25
Actions
Shares
0
Downloads
43
Comments
0
Likes
36
Embeds 0
No embeds

No notes for slide

An Internal of LINQ to Objects

  1. 1. An Internal of LINQ to Objects 2013/12/14 Yoshifumi Kawai - @neuecc
  2. 2. Self Introduction @仕事 株式会社グラニ 取締役CTO C# 5.0 + .NET Framework 4.5 + ASP.NET MVC 5 最先端C#によるハイパフォーマンスWebアプリケーション @個人活動 Microsoft MVP for Visual C# Web http://neue.cc/ Twitter @neuecc LINQがひじょーに好き、趣味はライブラリ作成
  3. 3. As Librarian NuGet公開パッケージ数30突破 https://www.nuget.org/profiles/neuecc/ linq.js JavaScriptにLINQ to Objectsを移植したライブラリ http://linqjs.codeplex.com/ その他色々 Chaining Assertion(ユニットテスト補助), DynamicJson(dynamicなJSON), AsyncOAuth(PCLのOAuthライブラリ), CloudStructures(Object/Redisマッパー), ReactiveProperty(Rx + MVVM), AnonymousComparer(LINQ補助), etc... 最近はOwinにムチューなのでMiddleware作ってきます
  4. 4. Session Target LINQ to Objectsを十分知っている人 知らない状態だと全体的にイミフかも? LINQ to Objectsという観点ではLEVEL 500を予定は未定 もっと極めて見たいと思う人向け 必ずどれかは知らなかった!というネタがあるはず もちろん、それ知ってるよ!もあるはず 使えないネタから使えないネタまで 基本的には使えないネタ多め、但し一部は非常に役立つかとは
  5. 5. Execution Pipeline
  6. 6. new Enumerable<T>() // メソッドチェーン Enumerable.Range(1, 10).Select(i => i * i).Take(5); // を、細分化すると? var rangeEnumerable = Enumerable.Range(1, 10); var selectEnumerable = rangeEnumerable.Select(i => i * i); var takeEnumerable = selectEnumerable.Take(5); takeEnumerable.GetEnumerator();
  7. 7. sourceの内包 Take Select Range
  8. 8. 下流から上流、上流から下流へ 最下層からのMoveNextの要求 が最上流まで連鎖し 最上流からCurrentの値が最下 層まで降りてくる
  9. 9. Immediate vs Deferred 即時実行(Immediate Execution) クエリ演算子を呼ぶとすぐに実行されるもの ToArray, First, Sum, etc... ようするにIEnumerable<T>ではなく具体的な何かを返すもの 遅延実行(Deferred Execution) クエリ演算子を呼んだ時点では実行されないもの Where, Select, OrderBy, etc... ようするにIEnumerable<T>を返すもの メソッドチェーンしても毎回具体的な何かを返さないから実行効率 やメモリ効率が良い、よって好きに繋げてください
  10. 10. Streaming vs Non-Streaming 遅延実行は、更に2つのカテゴリーに分けられる Streaming 必要なだけ少しずつ読み取っていく Where, Select, Concat, Union, Distinct, Take, etc... Non-Streaming 実行が開始された瞬間に全てのデータを読み込む OrderBy/Descending, ThenBy/Descending, GroupBy, Reverse Join, GroupJoin, Except, Intersectは2つ目のシーケンス側が
  11. 11. Non-Streaming? OrderBy/Reverse/GroupBy ソートするためには全部読み込まなきゃソートできない! 逆から列挙するには、まず全部読み込まなきゃ! グループ分けしたのを列挙するには、全部読み込まなきゃ! 一部集合演算系 Except/Intersectは、要素がもう片方の集合に含まれているか調 べるには、先に片方を全部読んでHashSet生成が必要 Join/GroupJoinも似たような感じ
  12. 12. Materialization LINQの実行は遅延する 何度でも実行される可能性がある もしそのシーケンスがUltra Heavyな処理を含んでいたら? 複数回読みたい場合は、ToArrayで 実体化(Materialization)することで 二度読みすることを防ぐ たまにIEnumerable<T>のまま ElementAtのラッシュしたりする人 がいますがマジヤヴァイのでやめて
  13. 13. IEnumerator<T> is IDisposable IEnumerable<T>がリソースを抱えている可能性 外部リソース(ファイル,DB, etc…)からの読み込みに便利 二度読みすると重かったり結果が変化したりしやすくて危険 static IEnumerable<string> EnumerateLines(string path) { using (var sr = new StreamReader(path)) { よって生のIEnumerator<T>を扱うと while (!sr.EndOfStream) { きは必ずusingすること!foreachや yield return sr.ReadLine(); LINQの標準クエリ演算子は全てusing } されています。これはmustです } }
  14. 14. Iterator Revisited
  15. 15. IEnumerable<T> LINQの中心となるインターフェイス LINQはIEnumerable<T>の連鎖 を、生成することが出来る言語機能 from C# 2.0 yield returnとyield break LINQ to Objectsはこれで実装されてる 自分で作るときも勿論これを使う
  16. 16. イテレータはいつ開始されるか static IEnumerable<int> Iter() { Console.WriteLine("IterStart"); yield return 1; Console.WriteLine("IterEnd"); } static void Main(string[] args) { Console.WriteLine("Before GetEnumerator"); var e = Iter().GetEnumerator(); Console.WriteLine("After GetEnumerator"); Console.WriteLine("Before MoveNext1"); e.MoveNext(); Console.WriteLine("After MoveNext1"); Console.WriteLine("Current:" + e.Current); Console.WriteLine("Before MoveNext2"); e.MoveNext(); Console.WriteLine("After MoveNext2"); }
  17. 17. イテレータはいつ開始されるか static IEnumerable<int> Iter() { Console.WriteLine("IterStart"); yield return 1; Console.WriteLine("IterEnd"); } Before GetEnumerator static void Main(string[] args) { Console.WriteLine("Before GetEnumerator"); var e = Iter().GetEnumerator(); Console.WriteLine("After GetEnumerator"); Console.WriteLine("Before MoveNext1"); e.MoveNext(); Console.WriteLine("After MoveNext1"); Console.WriteLine("Current:" + e.Current); Console.WriteLine("Before MoveNext2"); e.MoveNext(); Console.WriteLine("After MoveNext2"); } After MoveNext1 Current:1 Before MoveNext2 After GetEnumerator Before MoveNext1 IterStart IterEnd After MoveNext2 最初のMoveNextが呼ばれたタイ ミングで動き出す
  18. 18. State Machine Enumeratorは内部的に4つの状態を持つ 初期値はbefore MoveNextが呼ばれるとrunningになる yield returnに到達するとsuspendedになる 実行中はrunningとsuspendedが繰り返される running中のMoveNextの挙動は未定義、つまりスレッドセーフではない GetEnumeratorで得られる各Enumerator自体はスレッドセーフ、得られる Enumeratorがスレッドセーフではない、ということ コード末尾、もしくはyield breakに到達するとafterになる または例外がthrowされた場合も
  19. 19. 引数チェックのタイミング static IEnumerable<string> StringRepeat(string str, int count) { // 引数チェックしてるのに if (str == null) throw new ArgumentNullException(); for (int i = 0; i < count; i++) { yield return str; } 最初のMoveNextが呼ばれるまで 本体は動き出さない } static void Main(string[] args) { // 何も起こりません var repeat = StringRepeat(null, 100).Take(10); } MoveNextが呼ばれる = foreach or 即時 実行のLINQクエリ演算子を呼んだ時
  20. 20. 分離するというマナー public static IEnumerable<string> StringRepeat(string str, int count) { // 引数チェックは先に行って if (str == null) throw new ArgumentNullException(); return StringRepeatCore(str, count); メソッドを2つに分割する } // 本体はprivateに分離 private static IEnumerable<string> StringRepeatCore(string str, int count) { for (int i = 0; i < count; i++) { yield return str; } 全てのLINQ標準クエリ演算子がこれに } 従っています。自分でyield return使って 実装する時も従うべき
  21. 21. DeepDive OrderBy
  22. 22. Stable Sort and Unstable Sort 大雑把なソートの分類 破壊的か、非破壊的か OrderByはIEnumerable<T>が元で破壊不能というのもあり非破壊的 安定ソートか、非安定ソートか 安定ソートは同じ値が来た時、ソート前の順序が維持される OrderByのアルゴリズムはQuickSort QuickSortは非安定ソート OrderByは安定ソート ん?あれ?
  23. 23. Schewarizian Transform Map.Sort.Map シュワルツ変換、Perlでよく知られたイディオム 比較関数が重たい時に、一度かけるだけで済むので計算量を軽減できる OrderByはだいたいそれと似たような感じ 全要素をなめて元要素の配列を生成 →比較関数を適用した要素の配列を生成 ナンノコッチャ →0~lengthが要素のint配列(index)を生成 →要素配列を元に比較してindex配列をソート Array.Sort(keys, items)みたいなもの →ソート済みindexを列挙して元要素の配列と照らしあわせて列挙
  24. 24. with ThenBy ThenByも同様に先に全部比較関数を適用する たとえ、要素が被らなくてThenBy使われなくてもね! OrderByやThenByは便利だしカジュアルに 使っちゃうけれど、内部的には全LINQメ ソッドの中でも屈指のヘヴィさを誇ること を少しだけ気にとめよう
  25. 25. OrderBy as Shuffle, OrderBy as MaxBy Shuffleが欲しい? seq.OrderBy(_ => Guid.NewGuid()) Sortの比較関数にランダムは危険で偏りが出てしまう だけどOrderByの仕組みの場合は比較関数ではないので大丈夫 MaxBy, MinByが欲しい? seq.OrderBy(x => x.Age).First(); seq.OrderByDescending(x => x.Age).First(); さすがOrderBy、なんでもできる!そこにシビれる憧れれぅ!
  26. 26. No More 横着 Max取るのにソートとかどうかしてるよ 本来O(n)で済むのに! シャッフルだってもっと効率よくなる……よ? 定番どころではFisher-Yatesアルゴリズムとか お手軽にやるならOK、本格的に使っていくなら自作 書き捨てコードとかでは楽だし全然構わし平然とやるけど 弊社の社内ライブラリにはShuffleやMaxByなど定義してある
  27. 27. Grouping Grouping Grouping
  28. 28. GroupBy vs ToLookup そもそもToLookup知らない? 辞書のバリュー側がIEnumerableになってるもの ようは一つのキーに複数のバリューを格納できる 超便利!ほんと!超使う! で、どっちもグルーピング GroupByは遅延実行(非ストリーミング) ToLookupは即時実行 GroupByはToLookupしたあと即列挙するだけなので実は中身一緒
  29. 29. 列挙の順序の保証 例えばDictionaryは不定 GroupByの場合は保証されている キーがシーケンスに最初に現れた順 バリュー側の順序も出現順 これはToLookupでも同様 ToLookupとGroupByの中身は一緒だから ただしこちらはドキュメントに記載無しなので保証と捉えられ るかはグレーかもね
  30. 30. 複数キーによるグルーピング 匿名型を使おう! seq.GroupBy(x => new { x.Age, x.Name }); 匿名型はGetHashCodeとEqualsを全プロパティで比較して一致させ るので、判定要素に使える 裏にあるDictionaryを考える 集合系やグルーピングなど、IEqualityComparerを受けるオーバー ロードがあるものは裏でHashSetやDictionaryが暗躍している 要素の等しさを判定するのにIEqualityComparerを使うわけです
  31. 31. IEqualityComparerダルい 例えばDistinctで、重複除去を特定要素でだけしたい // こんな大げさなものを定義して public class AgeEqualityComparer : IEqualityComparer<Person> { public bool Equals(Person x, Person y) { return x.Age.Equals(y.Age); } public int GetHashCode(Person obj) { return obj.Age.GetHashCode(); } } // こうする。ダルい。Ageで比較したいだけなのに seq.Distinct(new AgeEqualityComparer()); .Distinct(x => x.Age)って書きたい
  32. 32. AnonymousComparer というものを公開しています http://linqcomparer.codeplex.com/ PM> Install-Package AnonymousComparer 何が出来る? AnonymousComparer.Create .Distinct(x => x.Age)って書ける! ラムダ式でIEqualityComparerを作成可能 LINQ標準クエリ演算子へのオーバーロード IEqualityComparerを受けるオーバーロードが全てラムダ式受け入れ可に
  33. 33. Query Expression
  34. 34. クエリ構文 vs メソッド構文 メソッド構文のほうが好き むしろクエリ構文嫌い メソッド構文のいいところ LINQ to Objectsの全てのメソッドが使える LINQ to Objectsの全てのオーバーロードが使える Selectのindex付きオーバーロードとかクエリ構文使えない IntelliSensable! IntelliSenseがあるからクエリ構文よりむしろ書きやすい
  35. 35. Transparent Identifier #from from a in source from b in source from c in source from d in source from e in source where a % 2 == 0 && b % 2 == 0 && c % 2 == 0 select string.Concat(a, b, c, d, e);
  36. 36. Transparent Identifier #let from x in source let a = x + x let b = x - x let c = x / x let d = x * x select a + b + c + d;
  37. 37. Language INtegreated Monad static void Main(string[] args) { // クエリ構文でusingを表現 var firstLines = from path in new[] { "foo.txt", "bar.txt" } from stream in File.OpenRead(path) // FileStreamがSelectManyの対象に from reader in new StreamReader(stream) // StreamReaderがSelectManyの対象に select path + "¥t" + reader.ReadLine(); } public static IEnumerable<TResult> SelectMany<TSource, TDisposable, TResult>( this IEnumerable<TSource> source, Func<TSource, TDisposable> disposableSelector, Func<TSource, TDisposable, TResult> resultSelector) where TDisposable : IDisposable { foreach (var item in source) { using (var disposableItem = disposableSelector(item)) Select, Where, SelectManyなどは { 名前が一致すれば、自作メソッド yield return resultSelector(item, disposableItem); } をクエリ構文で使うことが可能 } }
  38. 38. いろいろ変換されてすごーい けど、メソッド構文使うよね 機構としては素晴らしい、けど全然使わない JOINが書きやすいと言えなくもないけれど、IntelliSenseの効きやすさを勘 案すると、単発JOIN程度ならメソッド構文でも余裕 もちろん多重fromやletは便利ではあるけれど、必要になる頻度は…… クエリ構文では使えないメソッドが多すぎる クエリプロバイダのための制限 自由に書けるメソッド構文ではクエリプロバイダによる解釈を逸脱しがち なので、制限を加えるためのものとしてはいい、主にSQLで それでも結局、全て表現できなかったりで使えない
  39. 39. Internal Optimization
  40. 40. それAny if(seq.Count() != 0) vs if(seq.Any()) Countは最初から最後まで全て列挙して個数を計算する Anyは最初の一件があるかないかだけをチェックする Anyったらさいきょーね ま、こういうチェックってその後に別のメソッド を、呼んでたら2回列挙ですね? →前の方のMaterializeの話 ToArrayしますか、しましょうか
  41. 41. Count() vs Count Count()は全件列挙するからよくない? ICollection<T>を実装していれば、そちらのCountを使う (source as ICollection<T>).Count ElementAt, Last, LastOrDefault IList<T>を実装していればインデクサ経由でO(1) Lastは[xs.Length – 1]って書くのダルいので、普通に嬉しい Reverse ICollection<T>を実装していれば? CopyToを使って配列に一気にコピーしてから逆順forで列挙する IList<T>ではない、Indexer経由の遅さが気になるかもだからかな
  42. 42. There, There, Where, Where 連打 is 楽しい .Where(x => pred1(x)) .Where(x => pred2(x)) .Where(x => pred3(x)) それ&&? .Where(x => pred1(x) && pred2(x) && pred3(x)) パイプライン削減?効率厨め!
  43. 43. LINQ has Composability LINQの合成可能性を活かすとき var flag = true; var seq = Enumerable.Range(1, 10).Where(x => x % 2 == 0); if (flag) { seq = seq.Where(x => x % 3 == 0).Take(2); } &&に常にまとめられるとは限らない Funcも合成できるけどC#で関数合成は面倒だし または、.Where(predicate)が意味でまとまってる時 Where連打したほうがわかりやすいじゃない?
  44. 44. CombinePredicates CombinePredicatesの名の通り、 Where連打によるpredicateが&& で連結されている Where3つのはずだけど すぐ真上にRangeIterator
  45. 45. 連結の最適化 結合してくれるので連打でもOK Where.SelectとSelect.Selectも連結最適化される Where.SelectとSelect.Selectは同じイテレータ に入る(WhereSelectEnumerableIterator) Select連打はまとまってCombineSelectorsに。 また、Whereもpredicateとして保持
  46. 46. 連結の破壊 Select.Whereは壊れる Index使う系のオーバーロードも使うと壊れる 最下層にWhereEnumerable 中間にWhereSelectEnumerable 最上位層にRange
  47. 47. Empty Considered Harmful to Aggregation
  48. 48. 空シーケンスと集計 例外出ます。 Aggregateはseedつければ回避可能 var empty = Enumerable.Empty<int>(); // InvalidOperationException var product1 = empty.Aggregate((x, y) => x * y); // 0 var product2 = empty.Aggregate(0, (x, y) => x * y); // InvalidOperationException var max = empty.Max(); // InvalidOperationException var min = empty.Min(); // InvalidOperationException var avg = empty.Average(); Sumは0 // 0 var sum = empty.Sum();
  49. 49. 空シーケンスとNullable(or class) 例外出ません。 // int?の空シーケンス var empty = Enumerable.Empty<int?>(); null返します。 // InvalidOperationException var product = empty.Aggregate((x, y) => x * y); さすがにAggregateはダメ この振る舞いが妥当なのか は諸説あるとかないとか 極力、値を返すようにする、という観 点で見ればstructはデフォルト値がな いからダメでclassならあるので返せる から、と見たり // null var max = empty.Max(); // null var min = empty.Min(); // null var avg = empty.Average();
  50. 50. DefaultIfEmpty #割と忘れる // Minが欲しいけどWhereでフィルタした結果Emptyになってしまう // けど例外は避けたい! // わざわざToArrayしてAnyで確認してから、とかも嫌! var min = Enumerable.Range(1, 10) .Where(x => x > 10) .Cast<int?>() // Nullableにキャストして .Min() ?? -1; // nullのままが嫌なら??で値を // 素直にDefaultIfEmpty使いましょう:) var min = Enumerable.Range(1, 10) .Where(x => x > 10) .DefaultIfEmpty(-1) .Min();
  51. 51. 記号演算のジェネリクス制約ないけど? 気合!根性! そっ閉じ
  52. 52. Asynchronous LINQ 非同期時代のLINQ http://neue.cc/2013/12/04_435.html を、読んでください(
  53. 53. linq.js
  54. 54. LINQ to Objects for JavaScript JavaScript上で完全完璧に再現 ICollectionなどの最適化 => 入ってる Where.Whereなどの最適化 => 入ってる OrderByなどの仕組み => 入ってる 標準クエリ演算子を全収録+α プラスアルファでむしろ二倍以上のメソッド数 JavaScriptのための 文字列ラムダ式, TypeScript定義, IntelliSense用vsdoc
  55. 55. ver.3 Beta5,6,7,8,9,,, 2年近くベータ中! ゴメンナサイ 未リリース状態(gitリポジトリ上のみ)の更新 EqualityComparerの追加 これで今まで再現度98%ぐらいだったのが100%に Tupleの追加 AnonymousTypeの代用として、これでGroupByが超捗る その他細かい調整多数 次のベータリリースは1月か2月ぐらいで……!
  56. 56. Conclusion
  57. 57. まとめ 凄くシンプル、でも凄く奥が深い ちょっと実装するだけだととても単純なのだけど 潜れば潜るほど色々なことが発見できる LINQ to Everything LINQ to XML, Reactive Extensions Asynchronousに対する活用などなど沢山! LINQ to SQL, Entitiesは割とドウデモイイ

×