An Internal of LINQ to Objects

Post on 15-Jan-2015

29.143 views 4 download

description

LINQ Study #3

Transcript of An Internal of LINQ to Objects

An Internal of LINQ to Objects

2013/12/14Yoshifumi Kawai - @neuecc

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がひじょーに好き、趣味はライブラリ作成

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作ってきます

Session Target

LINQ to Objectsを十分知っている人

知らない状態だと全体的にイミフかも?

LINQ to Objectsという観点ではLEVEL 500を予定は未定

もっと極めて見たいと思う人向け

必ずどれかは知らなかった!というネタがあるはず

もちろん、それ知ってるよ!もあるはず

使えないネタから使えないネタまで

基本的には使えないネタ多め、但し一部は非常に役立つかとは

Execution Pipeline

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

sourceの内包Take

Select

Range

下流から上流、上流から下流へ

最下層からのMoveNextの要求が最上流まで連鎖し

最上流からCurrentの値が最下層まで降りてくる

Immediate vs Deferred

即時実行(Immediate Execution)

クエリ演算子を呼ぶとすぐに実行されるもの

ToArray, First, Sum, etc...

ようするにIEnumerable<T>ではなく具体的な何かを返すもの

遅延実行(Deferred Execution)

クエリ演算子を呼んだ時点では実行されないもの

Where, Select, OrderBy, etc...

ようするにIEnumerable<T>を返すもの

メソッドチェーンしても毎回具体的な何かを返さないから実行効率

やメモリ効率が良い、よって好きに繋げてください

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つ目のシーケンス側が

Non-Streaming?

OrderBy/Reverse/GroupBy

ソートするためには全部読み込まなきゃソートできない!

逆から列挙するには、まず全部読み込まなきゃ!

グループ分けしたのを列挙するには、全部読み込まなきゃ!

一部集合演算系

Except/Intersectは、要素がもう片方の集合に含まれているか調

べるには、先に片方を全部読んでHashSet生成が必要

Join/GroupJoinも似たような感じ

Materialization

LINQの実行は遅延する

何度でも実行される可能性がある

もしそのシーケンスがUltra Heavyな処理を含んでいたら?

複数回読みたい場合は、ToArrayで実体化(Materialization)することで

二度読みすることを防ぐ

たまにIEnumerable<T>のままElementAtのラッシュしたりする人がいますがマジヤヴァイのでやめて

IEnumerator<T> is IDisposable

IEnumerable<T>がリソースを抱えている可能性

外部リソース(ファイル,DB, etc…)からの読み込みに便利

二度読みすると重かったり結果が変化したりしやすくて危険

static IEnumerable<string> EnumerateLines(string path){

using (var sr = new StreamReader(path)){

while (!sr.EndOfStream){

yield return sr.ReadLine();}

}}

よって生のIEnumerator<T>を扱うときは必ずusingすること!foreachや

LINQの標準クエリ演算子は全てusing

されています。これはmustです

Iterator Revisited

IEnumerable<T>

LINQの中心となるインターフェイス

LINQはIEnumerable<T>の連鎖

を、生成することが出来る言語機能

from C# 2.0

yield returnとyield break

LINQ to Objectsはこれで実装されてる

自分で作るときも勿論これを使う

イテレータはいつ開始されるか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");

}

イテレータはいつ開始されるか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");

}

Before GetEnumerator

After GetEnumerator

Before MoveNext1

IterStart

After MoveNext1

Current:1

Before MoveNext2

IterEnd

After MoveNext2

最初のMoveNextが呼ばれたタイミングで動き出す

State Machine

Enumeratorは内部的に4つの状態を持つ

初期値はbefore

MoveNextが呼ばれるとrunningになる

yield returnに到達するとsuspendedになる

実行中はrunningとsuspendedが繰り返される

running中のMoveNextの挙動は未定義、つまりスレッドセーフではない

GetEnumeratorで得られる各Enumerator自体はスレッドセーフ、得られる

Enumeratorがスレッドセーフではない、ということ

コード末尾、もしくはyield breakに到達するとafterになる

または例外がthrowされた場合も

引数チェックのタイミング

static IEnumerable<string> StringRepeat(string str, int count){

// 引数チェックしてるのに

if (str == null) throw new ArgumentNullException();

for (int i = 0; i < count; i++){

yield return str;}

}

static void Main(string[] args){

// 何も起こりません

var repeat = StringRepeat(null, 100).Take(10);}

最初のMoveNextが呼ばれるまで本体は動き出さない

MoveNextが呼ばれる = foreach or 即時実行のLINQクエリ演算子を呼んだ時

分離するというマナーpublic static IEnumerable<string> StringRepeat(string str, int count){

// 引数チェックは先に行って

if (str == null) throw new ArgumentNullException();

return StringRepeatCore(str, count);}

// 本体はprivateに分離

private static IEnumerable<string> StringRepeatCore(string str, int count){

for (int i = 0; i < count; i++){

yield return str;}

}

メソッドを2つに分割する

全てのLINQ標準クエリ演算子がこれに従っています。自分でyield return使って

実装する時も従うべき

DeepDive OrderBy

Stable Sort and Unstable Sort

大雑把なソートの分類

破壊的か、非破壊的か

OrderByはIEnumerable<T>が元で破壊不能というのもあり非破壊的

安定ソートか、非安定ソートか

安定ソートは同じ値が来た時、ソート前の順序が維持される

OrderByのアルゴリズムはQuickSort

QuickSortは非安定ソート

OrderByは安定ソート

ん?あれ?

Schewarizian Transform

Map.Sort.Map

シュワルツ変換、Perlでよく知られたイディオム

比較関数が重たい時に、一度かけるだけで済むので計算量を軽減できる

OrderByはだいたいそれと似たような感じ

全要素をなめて元要素の配列を生成

→比較関数を適用した要素の配列を生成

→0~lengthが要素のint配列(index)を生成

→要素配列を元に比較してindex配列をソート

Array.Sort(keys, items)みたいなもの

→ソート済みindexを列挙して元要素の配列と照らしあわせて列挙

ナンノコッチャ

with ThenBy

ThenByも同様に先に全部比較関数を適用する

たとえ、要素が被らなくてThenBy使われなくてもね!

OrderByやThenByは便利だしカジュアルに使っちゃうけれど、内部的には全LINQメソッドの中でも屈指のヘヴィさを誇ること

を少しだけ気にとめよう

OrderBy as Shuffle, OrderBy as MaxBy

Shuffleが欲しい?

Sortの比較関数にランダムは危険で偏りが出てしまう

だけどOrderByの仕組みの場合は比較関数ではないので大丈夫

MaxBy, MinByが欲しい?

さすがOrderBy、なんでもできる!そこにシビれる憧れれぅ!

seq.OrderBy(_ => Guid.NewGuid())

seq.OrderBy(x => x.Age).First();seq.OrderByDescending(x => x.Age).First();

No More 横着

Max取るのにソートとかどうかしてるよ

本来O(n)で済むのに!

シャッフルだってもっと効率よくなる……よ?

定番どころではFisher-Yatesアルゴリズムとか

お手軽にやるならOK、本格的に使っていくなら自作

書き捨てコードとかでは楽だし全然構わし平然とやるけど

弊社の社内ライブラリにはShuffleやMaxByなど定義してある

Grouping Grouping Grouping

GroupBy vs ToLookup

そもそもToLookup知らない?

辞書のバリュー側がIEnumerableになってるもの

ようは一つのキーに複数のバリューを格納できる

超便利!ほんと!超使う!

で、どっちもグルーピング

GroupByは遅延実行(非ストリーミング)

ToLookupは即時実行

GroupByはToLookupしたあと即列挙するだけなので実は中身一緒

列挙の順序の保証

例えばDictionaryは不定

GroupByの場合は保証されている

キーがシーケンスに最初に現れた順

バリュー側の順序も出現順

これはToLookupでも同様

ToLookupとGroupByの中身は一緒だから

ただしこちらはドキュメントに記載無しなので保証と捉えられ

るかはグレーかもね

複数キーによるグルーピング

匿名型を使おう!

匿名型はGetHashCodeとEqualsを全プロパティで比較して一致させ

るので、判定要素に使える

裏にあるDictionaryを考える

集合系やグルーピングなど、IEqualityComparerを受けるオーバー

ロードがあるものは裏でHashSetやDictionaryが暗躍している

要素の等しさを判定するのにIEqualityComparerを使うわけです

seq.GroupBy(x => new { x.Age, x.Name });

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)って書きたい

AnonymousComparer

というものを公開しています

http://linqcomparer.codeplex.com/

PM> Install-Package AnonymousComparer

何が出来る?

AnonymousComparer.Create

ラムダ式でIEqualityComparerを作成可能

LINQ標準クエリ演算子へのオーバーロード

IEqualityComparerを受けるオーバーロードが全てラムダ式受け入れ可に

.Distinct(x => x.Age)って書ける!

Query Expression

クエリ構文 vsメソッド構文

メソッド構文のほうが好き

むしろクエリ構文嫌い

メソッド構文のいいところ

LINQ to Objectsの全てのメソッドが使える

LINQ to Objectsの全てのオーバーロードが使える

Selectのindex付きオーバーロードとかクエリ構文使えない

IntelliSensable!

IntelliSenseがあるからクエリ構文よりむしろ書きやすい

Transparent Identifier #from

from a in sourcefrom b in sourcefrom c in sourcefrom d in sourcefrom e in sourcewhere a % 2 == 0 && b % 2 == 0 && c % 2 == 0select string.Concat(a, b, c, d, e);

Transparent Identifier #let

from x in sourcelet a = x + xlet b = x - xlet c = x / xlet d = x * xselect a + b + c + d;

Language INtegreated Monadstatic 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)){

yield return resultSelector(item, disposableItem);}

}}

Select, Where, SelectManyなどは名前が一致すれば、自作メソッドをクエリ構文で使うことが可能

いろいろ変換されてすごーい

けど、メソッド構文使うよね

機構としては素晴らしい、けど全然使わない

JOINが書きやすいと言えなくもないけれど、IntelliSenseの効きやすさを勘

案すると、単発JOIN程度ならメソッド構文でも余裕

もちろん多重fromやletは便利ではあるけれど、必要になる頻度は……

クエリ構文では使えないメソッドが多すぎる

クエリプロバイダのための制限

自由に書けるメソッド構文ではクエリプロバイダによる解釈を逸脱しがち

なので、制限を加えるためのものとしてはいい、主にSQLで

それでも結局、全て表現できなかったりで使えない

Internal Optimization

それAny

if(seq.Count() != 0) vs if(seq.Any())

Countは最初から最後まで全て列挙して個数を計算する

Anyは最初の一件があるかないかだけをチェックする

Anyったらさいきょーね

ま、こういうチェックってその後に別のメソッド

を、呼んでたら2回列挙ですね?

→前の方のMaterializeの話

ToArrayしますか、しましょうか

Count() vs Count

Count()は全件列挙するからよくない?

ICollection<T>を実装していれば、そちらのCountを使う

(source as ICollection<T>).Count

ElementAt, Last, LastOrDefaultIList<T>を実装していればインデクサ経由でO(1)

Lastは[xs.Length – 1]って書くのダルいので、普通に嬉しい

ReverseICollection<T>を実装していれば?

CopyToを使って配列に一気にコピーしてから逆順forで列挙する

IList<T>ではない、Indexer経由の遅さが気になるかもだからかな

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

パイプライン削減?効率厨め!

LINQ has Composability

LINQの合成可能性を活かすとき

&&に常にまとめられるとは限らない

Funcも合成できるけどC#で関数合成は面倒だし

または、.Where(predicate)が意味でまとまってる時

Where連打したほうがわかりやすいじゃない?

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

CombinePredicates

Where3つのはずだけどすぐ真上にRangeIterator

CombinePredicatesの名の通り、Where連打によるpredicateが&&

で連結されている

連結の最適化

結合してくれるので連打でもOK

Where.SelectとSelect.Selectも連結最適化される

Where.SelectとSelect.Selectは同じイテレータに入る(WhereSelectEnumerableIterator)

Select連打はまとまってCombineSelectorsに。また、Whereもpredicateとして保持

連結の破壊

Select.Whereは壊れる

Index使う系のオーバーロードも使うと壊れる

最下層にWhereEnumerable

最上位層にRange

中間にWhereSelectEnumerable

Empty Considered Harmfulto Aggregation

空シーケンスと集計

例外出ます。var empty = Enumerable.Empty<int>();

// InvalidOperationExceptionvar product1 = empty.Aggregate((x, y) => x * y);

// 0var product2 = empty.Aggregate(0, (x, y) => x * y);

// InvalidOperationExceptionvar max = empty.Max();

// InvalidOperationExceptionvar min = empty.Min();

// InvalidOperationExceptionvar avg = empty.Average();

// 0var sum = empty.Sum();

Aggregateはseedつければ回避可能

Sumは0

空シーケンスとNullable(or class)

例外出ません。

null返します。

さすがにAggregateはダメ

この振る舞いが妥当なのかは諸説あるとかないとか

// int?の空シーケンス

var empty = Enumerable.Empty<int?>();

// InvalidOperationExceptionvar product = empty.Aggregate((x, y) => x * y);

// nullvar max = empty.Max();

// nullvar min = empty.Min();

// nullvar avg = empty.Average();

極力、値を返すようにする、という観点で見ればstructはデフォルト値がないからダメでclassならあるので返せる

から、と見たり

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

記号演算のジェネリクス制約ないけど?

気合!根性!

そっ閉じ

Asynchronous LINQ非同期時代のLINQ http://neue.cc/2013/12/04_435.html

を、読んでください(

linq.js

LINQ to Objects for JavaScript

JavaScript上で完全完璧に再現

ICollectionなどの最適化 => 入ってる

Where.Whereなどの最適化 => 入ってる

OrderByなどの仕組み => 入ってる

標準クエリ演算子を全収録+α

プラスアルファでむしろ二倍以上のメソッド数

JavaScriptのための

文字列ラムダ式, TypeScript定義, IntelliSense用vsdoc

ver.3 Beta5,6,7,8,9,,,

2年近くベータ中!

ゴメンナサイ

未リリース状態(gitリポジトリ上のみ)の更新

EqualityComparerの追加

これで今まで再現度98%ぐらいだったのが100%に

Tupleの追加

AnonymousTypeの代用として、これでGroupByが超捗る

その他細かい調整多数

次のベータリリースは1月か2月ぐらいで……!

Conclusion

まとめ

凄くシンプル、でも凄く奥が深い

ちょっと実装するだけだととても単純なのだけど

潜れば潜るほど色々なことが発見できる

LINQ to Everything

LINQ to XML, Reactive Extensions

Asynchronousに対する活用などなど沢山!

LINQ to SQL, Entitiesは割とドウデモイイ