Upload
yoshifumi-kawai
View
29.142
Download
4
Embed Size (px)
DESCRIPTION
LINQ Study #3
Citation preview
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は割とドウデモイイ