Archive - Rx
LINQ to Objects & Interactive Extensions & linq.js 全メソッド概説
@ITに以前書いたLINQの基礎知識の話が載りました -> LINQの仕組み&遅延評価の正しい基礎知識 - @IT。ああ、もっとしっかり書いていれば(図もへっぽこだし)、と思ったり思わなかったり。それでも校正していただいたのと、細部は修正してあるので、元のものよりも随分と読みやすいはずです。そういえばで1月頭の話なんですね、姉妹編としてRxの基礎知識もやるつもりだったのにまだやってないよ!
ところでそもそも基礎知識といったら標準クエリ演算子が何をできるかではないのでしょうか?知ってるようで知らない標準クエリ演算子。101 LINQ SamplesもあるしMSDNのリファレンスは十分に充実していますが、しかし意外と見逃しもあるかもしれません。また、Interactive Extensionsで何が拡張されているのかは知っていますか?ついでにJS実装のlinq.jsには何があるのか知っていますか?
そんなわけで、LINQ to Objects、Ix、linq.jsの全メソッドを一行解説したいと思います。
LINQ to Objects
いわゆる、標準クエリ演算子。.NET 3.5から使えます。.NET4.0からはZipメソッドが追加されました。なお、サンプルと実行例はlinq.js Referenceに「完全に」同じ挙動をするJS実装での例がありますので、そちらを参照にどうぞ。こういう場合はJS実装だと便利ですね。
| Aggregate | 汎用的な値算出 |
| All | 条件に全て一致するか |
| Any | 条件に一つでも一致するか、引数なしの場合は空かどうか |
| AsEnumerable | IEnumerable<T>へアップキャスト |
| Average | 平均 |
| Cast | 値のダウンキャスト、主な用途はIEnumerableからIEnumerable<T>への変換 |
| Concat | 引数のシーケンスを後ろに連結 |
| Contains | 値が含まれているか、いわばAnyの簡易版 |
| Count | シーケンスの件数 |
| DefaultIfEmpty | シーケンスが空の場合、デフォルト値を返す(つまり長さ1) |
| Distinct | 重複除去 |
| ElementAt | 指定インデックスの要素の取得 |
| ElementAtOrDefault | 指定インデックスの要素の取得、なければデフォルト値を返す |
| Empty | 空シーケンスの生成 |
| Except | 差集合・差分だけ、集合なので重複は除去される |
| First | 最初の値の取得、ない場合は例外が発生 |
| FirstOrDefault | 最初の値を取得、ない場合はデフォルト値を返す |
| GroupBy | グループ化、ToLookupの遅延評価版(ただしストリーミングでの遅延評価ではない) |
| GroupJoin | 右辺をグループにして結合、外部結合をしたい時にDefaultIfEmptyと合わせて使ったりもする |
| Intersect | 積集合・共通の値だけ、集合なので重複は除去される |
| Join | 内部結合 |
| Last | 最後の値を取得、ない場合は例外が発生 |
| LastOrDefault | 最後の値を取得、ない場合はデフォルト値を返す |
| LongCount | シーケンスの件数、longなので長い日も安心 |
| Max | 最大値 |
| Min | 最小値 |
| OfType | 指定した型の値だけを返す、つまりWhereとisが組み合わさったようなもの |
| OrderBy | 昇順に並び替え |
| OrderByDescending | 降順に並び替え |
| Range | 指定個数のintシーケンスの生成 |
| Repeat | 一つの値を繰り返すシーケンスの生成 |
| Reverse | 逆から列挙 |
| Select | 射影、関数の第二引数はインデックス |
| SelectMany | シーケンスを一段階平らにする、モナドでいうbind |
| SequenceEqual | 二つのシーケンスを値で比較 |
| Single | 唯一の値を取得、複数ある場合は例外が発生 |
| SingleOrDefault | 唯一の値を取得、複数ある場合はデフォルト値を返す |
| Skip | 指定個数だけ飛ばす |
| SkipWhile | 条件が正のあいだ飛ばす |
| Sum | 合計 |
| Take | 指定個数列挙、シーケンスの個数より多く指定した場合はシーケンスの個数分だけ |
| TakeWhile | 条件が正のあいだ列挙 |
| ThenBy | 同順の場合のソートキーの指定、昇順に並び替え |
| ThenByDescending | 同順の場合のソートキーの指定、降順に並び替え |
| ToArray | 配列に変換 |
| ToDictionary | 辞書に変換 |
| ToList | リストに変換 |
| ToLookup | 不変のマルチ辞書(一つのキーに複数の値を持つ)に変換 |
| Union | 和集合・両方の値全て、集合なので重複は除去される |
| Where | フィルタ |
| Zip | 二つのシーケンスの結合、長さが異なる場合短いほうに合わされる |
暗記する必要はなくて、なんとなくこういうのがあってこんな名前だったかなー、とぐらいに覚えておけば、IntelliSenseにお任せできるので、それで十分です。
リスト処理という観点からみるとLINQはかなり充実しているわけですが、更に他の言語と比較した場合の特色は、やはりクエリ構文。SelectManyへの構文は多くの言語が備えていますが(モナドの驚異を参照のこと、LINQはLINM:言語統合モナドである、というお話)、SQLの構文をベースにしたJoin、GroupBy、OrderByへの専用記法は、意外と、というか普通に便利。
特にJoinはあってよかったな、と思います、インメモリで色々なところからデータ引っ張ってきて結合などすると特に。一つぐらいの結合なら別にメソッド構文でいいのですが、フツーのSQLと同じように大量のjoinを並べる場合に、クエリ構文じゃないとシンドい。インメモリからデータベースまで統一的な記法で扱える、ということの凄さを実感するところ。
といっても、普段はほとんどメソッド構文で書いてるんですけどねー。あくまで、込み入った状況になるときだけクエリ構文にしています。クエリ構文では表現できないものが結構多いわけで、わざわざ、これはクエリ構文だけで表現できるからクエリ構文にするかー、とか考えるのもカッタルイので。あと、単純にIntelliSenseでポコポコ打ってるほうが快適、というのもあります。
クエリ構文は、モナドへの記法というよりも、強力なリスト内包表記といった印象も、HaskellへのOrder By, Group Byのペーパー見て思ったりなんかしたりして。
Ix
Ix(Interactive Extensions)はReactive Extensionsで、現在は実験的なものとして提供されている、Enumerableの拡張メソッド群。NuGetのIx_Experimental-Mainで入れるのが使いやすい感じ。InfoQ: LINQ to Objectsのためのインタラクティブエクステンションに解説が少し出ていましたが、少し不足していたり、間違っていたり(DoWhileとTakeWhileは一見似ていますが、挙動は全然異なるし、Forは別に全く興味深くなくSelectManyと同じです)したので、こちらの方が正しいです(キリッ
| Buffer | 指定個数分に区切って配列で値を列挙 |
| Case | 引数のIDictionaryを元に列挙するシーケンスを決める、辞書に存在しない場合はEmpty |
| Catch | 例外発生時に代わりに後続のシーケンスを返す |
| Concat | 可変長引数を受け入れて連結する生成子、拡張メソッド版はシーケンスのシーケンスを平らにする |
| Create | getEnumeratorを渡し任意のIEnumerableを生成する、といってもEnumerator.Createがないため、あまり意味がない |
| Defer | シーケンスの生成をGetEumerator時まで遅延 |
| Distinct | 比較キーを受け入れるオーバーロード |
| DistinctUntilChanged | 同じ値が続くものを除去 |
| Do | 副作用として各値にActionを適用し、値をそのまま列挙 |
| DoWhile | 一度列挙後に条件判定し、合致すれば再列挙 |
| Expand | 幅優先探索でシーケンスを再帰的に平らにする |
| Finally | 列挙完了時に指定したActionを実行 |
| For | SelectManyと一緒なので存在意義はない(Rxと鏡にするためだけに存在) |
| ForEach | foreach、関数の第二引数はインデックス |
| Generate | forループを模した初期値、終了判定、増加関数、値成形関数を指定する生成子 |
| Hide | IEnumerable<T>に変換、具象型を隠す |
| If | 条件が正なら指定したシーケンスを、負なら指定したシーケンス、もしくはEmptyで列挙する |
| IgnoreElements | 後に続くメソッドに何の値も流さない |
| IsEmpty | シーケンスが空か、!Any()と等しい |
| Max | IComparer<T>を受け入れるオーバーロード |
| MaxBy | 指定されたキーで比較し最大値だった値を返す |
| Memoize | メモ化、複数回列挙する際にキャッシュされた値を返す |
| Min | IComparer<T>を受け入れるオーバーロード |
| MinBy | 指定されたキーで比較し最小値だった値を返す |
| OnErrorResumeNext | 例外が発生してもしなくても後続のシーケンスを返す |
| Publish | ShareとMemoizeが合わさったような何か |
| Repeat | 無限リピート生成子、拡張メソッドのほうは列挙後に無限/指定回数最列挙 |
| Retry | 例外発生時に再度列挙する |
| Return | 単一シーケンス生成子 |
| Scan | Aggregateの算出途中の値も列挙する版 |
| SelectMany | 引数を使わず別のシーケンスに差し替えるオーバーロード |
| Share | 列挙子を共有 |
| SkipLast | 後ろからn個の値をスキップ |
| StartWith | 先頭に値を連結 |
| TakeLast | 後ろからn個の値だけを列挙 |
| Throw | 例外が発生するシーケンス生成子 |
| Using | 列挙完了後にDisposeするためのシーケンス生成子 |
| While | 列挙前に条件判定し合致したら列挙し、終了後再度条件判定を繰り返す生成子 |
みんな実装したことあるForEachが載っているのが一番大きいのではないでしょうか。別に自分で実装するのは簡単ですが、公式に(といってもExperimental Releaseですが)あると、全然違いますから。なお、何故ForEachが標準クエリ演算子にないのか、というのは、“foreach” vs “ForEach” - Fabulous Adventures In Codingによれば副作用ダメ絶対とのことで。納得は……しない。
Ixに含まれるメソッドは標準クエリ演算子では「できない」もしくは「面倒くさい」。Ixを知ることは標準だけでは何ができないのかを知ること。何ができないのかを知っていれば、必要な局面でIxを使うなり自前実装するなりといった対応がすぐに取れます、無理に標準クエリ演算子をこねくり回すことなく。例えばBufferやExpandは非常に有益で、使いたいシチュエーションはいっぱいあるんですが、標準クエリ演算子ではできないことです。
While, DoWhileとTakeWhileの違いは条件判定する箇所。While,DoWhileは列挙完了前/後に判定し、判定がtrueならシーケンスを再び全て列挙する。TakeWhileは通る値で毎回判定する。
PublishとMemoizeの違いは難解です。Memoizeは直球そのままなメモ化なんですが、Publishが凄く説明しづらくて……。Enumerator取得まではShareと同じく列挙子の状態は共有されてるんですが、取得後はMemoizeのようにキャッシュした値を返すので値の順番は保証される、といった感じです。うまく説明できません。
存在意義が微妙なものも、それなりにありますね。例えばIfとCaseとForなどは、正直、使うことはないでしょう。Usingも、これを使うなら別メソッドに分けて、普通にusing + yield returnで書いてしまうほうが良いと私は考えています。
Ixを加えると、ほとんど全てをLINQで表現出来るようになりますが、やりすぎて解読困難に陥ったりしがちなのには少し注意を。複雑になるようならベタベタ書かずに、一定の塊にしたものを別メソッドに分ければいいし、分けた先では、メソッドを組み合わせるよりも、yield returnで書いたほうが素直に表現出来るかもしれません。
適切なバランス感覚を持って、よきLINQ生活を!
linq.js
LINQ to ObjectsのJavaScript実装であるlinq.jsにも、標準クエリ演算子の他に(作者の私の趣味で)大量のメソッドが仕込んであるので、せっかくなのでそれの解説も。標準クエリ演算子にあるものは省きます(挙動は同一なので)。また、C#でIEqualityComparer<T>を受け取るオーバーロードは、全てキーセレクター関数のオーバーロードに置き換えられています。
一行サンプルと実行はlinq.js Referenceのほうをどうぞ。
| Alternate | 値の間にセパレーターを織り込む、HaskellのIntersperseと同じ |
| BufferWithCount | IxのBufferと同じ、次のアップデートでBufferに改称予定 |
| CascadeBreadthFirst | 幅優先探索でシーケンスを再帰的に平らにする、IxのExpandと同じ |
| CascadeDepthFirst | 深さ優先探索でシーケンスを再帰的に平らにする |
| Catch | IxのCatchと同じ |
| Choice | 引数の配列、もしくは可変長引数をランダムに無限に列挙する生成子 |
| Cycle | 引数の配列、もしくは可変長引数を無限に繰り返す生成子 |
| Do | IxのDoと同じ |
| Finally | IxのFinallyと同じ |
| Flatten | ネストされた配列を平らにする |
| Force | シーケンスを列挙する |
| ForEach | IxのForEachと同じ |
| From | 配列やDOMなど長さを持つオブジェクトをEnumerableに変換、linq.jsの要の生成子 |
| Generate | ファクトリ関数を毎回実行して値を作る無限シーケンス生成子、IxのGenerateとは違う(IxのGenerateはUnfoldで代用可) |
| IndexOf | 指定した値を含む最初のインデックス値を返す |
| Insert | 指定したインデックスの箇所に値を挿入、Insert(0, value)とすればIxのStartWithと同じ |
| LastIndexOf | 指定した値を含む最後のインデックス値を返す |
| Let | 自分自身を引数に渡し、一時変数を使わず自分自身に変化を加えられる |
| Matches | 正規表現のマッチ結果をシーケンスとして列挙する生成子 |
| MaxBy | IxのMaxByと同じ |
| MemoizeAll | IxのMemoizeと同じ、次のアップデートでMemoizeに改称予定 |
| MinBy | IxのMinByと同じ |
| Pairwise | 隣り合う要素とのペアを列挙 |
| PartitionBy | キーで指定した同じ値が続いているものをグループ化する |
| RangeDown | 指定個数のマイナス方向数値シーケンス生成子 |
| RangeTo | 指定した値まで(プラス方向、マイナス方向)の数値シーケンス生成子 |
| RepeatWithFinalize | 単一要素の無限リピート、列挙完了時にその要素を受け取る指定した関数を実行 |
| Return | IxのReturnと同じ |
| Scan | IxのScanと同じ |
| Share | IxのShareと同じ |
| Shuffle | シーケンスをランダム順に列挙する |
| TakeExceptLast | IxのSkipLastと同じ |
| TakeFromLast | IxのTakeLastと同じ |
| ToInfinity | 無限大までの数値シーケンス生成子 |
| ToJSON | シーケンスをJSON文字列に変換(組み込みのJSON関数のあるブラウザかjson2.jsの読み込みが必要) |
| ToNegativeInfinity | マイナス無限大までの数値シーケンス生成子 |
| ToObject | JSのオブジェクトに変換 |
| ToString | 文字列として値を連結 |
| Trace | console.logで値をモニタ |
| Unfold | Aggregateの逆、関数を連続適用する無限シーケンス生成子 |
| Write | document.writelnで値を出力 |
| WriteLine | document.writeln + <br />で値を出力 |
| TojQuery | シーケンスをjQueryオブジェクトに変換 |
| toEnumerable | jQueryの選択している複数の要素を単一要素のjQueryオブジェクトにしてEnumerableへ変換 |
| ToObservable | 引数のSchduler上で(デフォルトはCurrentThread)Observableへ変換 |
| ToEnumerable | Cold ObservableのみEnumerableへ変換 |
Ixと被るものもあれば、そうでもないものも。ToStringなどは分かりやすく便利でよく使うのではかと。ToJSONもいいですね。Fromは拡張メソッドのない/prototype汚染をしないための、JavaScriptだけのためのメソッド。Matchesは地味に便利です、JSの正規表現は使いやすいようでいて、マッチの列挙はかなり面倒くさいので、そこを解消してくれます。linq.jsは移植しただけ、ではあるんですが、同時に移植しただけではなくて、JavaScriptでLINQはどうあるべきか、どうあると便利なのか、という考えに基づいて調整されています。
JavaScriptにはyield returnがないので(Firefoxにはyieldありますが)、シーケンスは全て演算子の組み合わせだけで表現できなければならない。というのが、手厚くメソッドを用意している理由でもあります。これだけあれば何だって作れるでしょう、きっと多分恐らく。
まとめ
これで今日からLINQ to Objectsマスター。Rx版もそのうち書きます(以前にReactive Extensions入門 + メソッド早見解説表を書きましたが、今は結構変わってしまいましたからね)。
Deep Dive AsEnumerable
AsEnumerable、といったらLINQのAsEnumerableです。その挙動は、IEnumerable<T>へと型変換をします。それだけ、なので実に影が薄いのですが、それでいて奥深く使いこなしが求められる、はずなのですが陰が薄いので無視されている感がなきにしもあらずなので、しっかりと紹介したいと思います。
AsEnumerableの実装
実装は非常に単純明快で、中身ほとんど空っぽです。
public static IEnumerable<T> AsEnumerable<T>(this IEnumerable<T> source) { return source; }
ようするにアップキャストです。どういう時に使えばいいかというと、例えば可変長引数とIEnumerable<T>の両方を受けたいオーバーロードを作る場合。
public void Show(params string[] values) { Show(values.AsEnumerable()); } public void Show(IEnumerable<string> values) { foreach (var item in values) { Console.WriteLine(item); } }
foreachでグルグル値を取り出すだけなので、IEnumerable<T>で受けるようにしたい。でも利便性のため可変長引数も用意しておきたい。という場合はよくあります。なので毎回このオーバーロードを用意するんですが、その時に、こうしてAsEnumerableを使います。なお、AsEnumerableを忘れると無限に再帰してStackOverflowしてしまいます……。
AsEnumerableがラップするのではなく、ただのアップキャストにすぎないということは重要です。以前にLinqとCountの効率でも書きましたが、LINQの一部のメソッドはIList<T>であったりICollection<T>であるとき、asやisを使って最適化を図ります。foreachするだけだとあまり関係ないですが、受け取ったIEnumerable<T>を使ってLINQで処理する場合だと、このことが効いてきます。
ならば常にアップキャストでよくて、ラップなど必要ないのではないか?というと必ずしもそうではありません。アップキャストは、ダウンキャストを可能にします。
// アップキャストされている状態というのは var numbers = new List<int>().AsEnumerable(); // ダウンキャストが可能にするということ、そして、ダウンキャストは危険 var list = (List<int>)numbers;
ダウンキャストが危険だ、やるな、というのなら、そもそもアップキャストをすべきではない。抽象で受けることこそがオブジェクト指向だとか、形だけのパターンにはまってるとそうなる。原則は比較的シンプルで。メソッドのシグネチャにおいて、引数の型は最大に受け入れるため出来る限り抽象で、戻り値の型は最大に利用出来るようにするため具象にすればいい。ローカル変数に関しては、原則varでよし。どうしても必要ならば、ローカル変数側、つまりメソッドの利用側が安全なアップキャストで適宜、抽象で受ければよいでしょう。
ダウンキャストはダメ基本的に。そして、ダウンキャストは可能な状態にすること自体がダメなので、アップキャストも最小限に。というのがメソッド定義の基本だと思っていますが、プロパティだと少し事情は変わってくるかも。一々ラップすることのパフォーマンスロスや手間を考えると、しかたがなくアップキャストで提供するのも、ありかなー、とは。
Hide
そんなわけで、具象型を消去して、完全にラップしてIEnumerable<T>として提供したいという場合もあるかと思います。そこで、Ix(Interactive Extensions、Reactive Extensionsのオマケで提供されているEnumerableの拡張メソッド群、NuGetのIx_Experimental-Mainで入れるのが手っ取り早い。Experimentalのとおり、まだ実験的な代物で保証されていないことは注意)にはHideというものがあります。これも実装は単純明快で
public static IEnumerable<T> Hide<T>(this IEnumerable<T> source) { foreach (var item in source) { yield return item; } }
といった形。Hideというメソッド名は具体的な型を隠す、といった意味合いで付けられているのでしょうね。
Rx(AsObservable)の場合
Enumerableと関連性の深いObservable、Rxにも同様に型変換をするAsObservableというメソッドが用意されています。主に使うシチュエーションは、Subjectの隠蔽をするときでしょうか。
// 5秒後に非同期で値を返すというだけのもの public static IObservable<T> SendValueAfter5Seconds<T>(T value) { var asyncSubject = new AsyncSubject<T>(); ThreadPool.QueueUserWorkItem(_ => { Thread.Sleep(TimeSpan.FromSeconds(5)); // とりまsleep asyncSubject.OnNext(value); // AsyncSubjectのキャッシュへ値送信 asyncSubject.OnCompleted(); // 非同期処理完了の合図(ここでObserverに値が送られる) }); return asyncSubject.AsObservable(); }
このコード自体には何の意味もありません、非同期処理を模して、スレッドプールで5秒待って値を送る、というだけです。大事なのはAsyncSubjectをAsObservableして返していること。このAsObservableはただのアップキャストではなく、新しい型でラップして具象型(AsyncSubject)を隠しています。つまり、AsEnumerableではなくHideに等しい挙動です。ここで、もしAsObservableを書いていないと
// return時にAsObservableが書かれていないとダウンキャスト可能になる var subject = (AsyncSubject<int>)SendValueAfter5Seconds(100); subject.Subscribe(Console.WriteLine); // なので、外側から発火可能になってしまう、これは最悪 subject.OnNext(-1); subject.OnCompleted();
Subject(標準だと4種類ある)は、Rxにおけるイベントの表現です。C#でのイベントは、内部からは発火可能、外側からは購読しかできない。というようになっていると思います。その挙動にするために、また、純粋に安全性のために、Subjectを購読させるために外側に出す場合は、AsObservableでラップして型を消さなければなりません。
※極初期(RxがReactive Frameworkと言われていた頃なぐらいに前)は、このAsObservableはHideというメソッド名でした。AsObservableのほうが分かりやすくて良いとは思いますが、Enumerableでの挙動と合わせるなら、キャストするだけのAsObservableとHideに分けるべきだったのでは?と思わなくは全くないです←Rxにおいてはただのキャストしただけのものは使う機会ないと思うので、現在の形で正解
IQueryableにおけるAsEnumerableの重要性
Enumerable、Observableと来たので、QueryableでのAsEnumerableも見てみましょう。QueryableにおけるAsEnumerableは、クエリ構築の終了です。IQueryableでのクエリ構築をそこで打ち切るというスイッチです。どういうことか、というと
// とあるContextによるQueryableはSkipWhileとCountをサポートしていなかったとします var count = toaru.createContext() // IQueryeable<T>とする .Where(x => x % 2 == 0) .SkipWhile(x => x < 100) .Count(); // 未サポートなのでExceptionが来る! // そういう場合、ToListするといい、というアドバイスがよく上がります var count = toaru.createContext() .Where(x => x % 2 == 0) .ToList() // ここまでの式でクエリ生成+List化 .SkipWhile(x => x < 100) // ここからはIEnumerable<T> .Count(); // でも、それならAsEnumerableでいいんだよ? var count = toaru.createContext() .Where(x => x % 2 == 0) .AsEnumerable() // 後続がGetEnumeratorを呼んだ時にここまででクエリ生成 .SkipWhile(x => x < 100) // ここからはIEnumerable<T> .Count();
Queryableの連鎖で、例えばLinq to SqlだったらSQL文を作っていきます。で、foreachであったりToListであったりをすると、SQLが作られて発行されてデータベースと通信されて。それって、どのタイミングでQueryableの中の式木がSQL文に変換されるかというと、GetEnumeratorが呼ばれた時、です。それはいつ呼ばれるの?というと、foreachされたりToListされたり、AsEnumerableしてその後のEnumerableのメソッドがGetEnumeratorを呼んだ、その時。
こんな感じです。ToArrayやToListは、そこで実体化するので、メソッドチェーンの後続がIEnumerable<T>なのは当然のことですが、AsEnumerableがただのキャストにすぎないのに意味合いが変化するのは、拡張メソッドの解決の優先度のため。型がIQueryable<T>の状態だとWhereやSelectはQueryableのWhereやSelectが選択されますが、型がIEnumerable<T>の状態だとEnumerableのWhereやSelectが選択される、ということです。Enumerable自体は遅延評価なので、後続のIEnumerable<T>がGetEnumeratorを呼び出したときに評価が開始されるのは変わらず。
AsEnumerableやToArray、ToListは実はQueryableクラスにはありません。なので、素の状態で拡張メソッドの解決がIEnumerable<T>側を呼び出すようになっています。
ところでクエリ文の構築はGetEnumeratorが呼ばれた時と言いましたが、GetEnumeratorを呼ばないとき、例えばQueryableでのFirstやSumはどうなっているのかというと、内部でExecuteが呼ばれた時です。IQueryProviderはこんなインターフェイス。
public interface IQueryProvider { IQueryable<TElement> CreateQuery<TElement>(Expression expression); TResult Execute<TResult>(Expression expression); // 非ジェネリックなものもありますが省略 }
FirstやSumなど、単独の結果を返すものは内部でExecuteを呼びます。なので、クエリプロバイダの実装次第ですが、通常はこのExecuteが呼ばれた時にクエリ文の構築と実行を同時に行うものと思われます。SelectやWhereなど、後続にIQueryableのチェーンを繋げるものは、内部でCreateQueryのほうを呼びます。そして最終的に複数の結果(IEnumerable<T>)を返す場合は、GetEnumeratorが呼ばれた時にクエリ文の構築と実行を行うものと思われます。
まとめ
AsEnumerableは、ようするにただのキャストなだけですが、その果たしている役割というものを考えると非常に深い。その割には(QueryableでToListばかり使われたりと)今ひとつ知名度に欠ける気もしますので、ドサッと紹介を書いてみました。ただのキャストだって語ろうと思えば幾らでも語れるLINQは素敵ですね!
非同期の再帰的な辿り方、或いはRxとC# 5.0 Asyncの連携について
例えば、ページを辿る。何度もアクセスを繰り返して、辿る。非同期で。単純なようで、やってみると何気にこれが結構難しい。例としてコードレシピのReactive Extensionsを使用してTwitterから非同期にデータを取得し表示するがありました。MVVMも絡めて、素晴らしいサンプルですね!
というわけで、お題を拝借して、Twitter ApiのGET statuses/friendsを使わせて頂きます。んが、今回は、手を抜いてフォロワーのscreen_name(@hogehogeのhogehogeの部分)だけを取れれば良い、ということにします。JSON解析やデシリアライズも面倒だし話の本題でもないので省略するため、DynamicJsonを使って、JSONを生のまんまっぽく扱うことにします。DynamicJsonは便利だなあ(棒)
さて、まずTwitter APIのcursorですが、大体こんな風になっています。目的はカーソルを辿って全てのuser(に含まれるscreen_name)を集めること。
JSON取得毎にnext_cursor_strという、次のページへのIDが取れるので、それを辿っていって、0が出たらページ末尾。といった具合です。next_cursor_strの値は一見ランダムに見える整数(2121409421とかそんな値になっている)であり、next_cursorという数字のものもあるのに、_strという文字列として得られるほうを使っています。何故かというと、TwitterのステータスIDが53bitを越えたお話 - tmytのらくがきを参照ください。DynamicJsonでは数字(Number)はdoubleとして扱うので、_strのほうを使わないと、危ういわけです。
まあ、ただのお題で本題な話ではないので、その辺は深く考えずそういうものなのだなあ、ぐらいで。
同期とyield returnと非同期
コードレシピのサンプルを見させて頂いたのですが、ネットワークアクセス部分がOpenReadなので、非同期”ではない”です。でも挙動は非同期だよ?というのは、Scheduler.ThreadPoolを使っているからなわけですが、つまるところ挙動的にはBackgroundWorkerを使って非同期にするのと同じことです。その場合ですと、Generateも確かに良いのですが、APIへのアクセスがそもそも同期であるならば、難しく考える必要はなく、yield returnを使ったほうが簡単です。単純なものは演算子の組み合わせで、複雑なものは素直に偉大なるコンパイラ生成(yield return)に頼る。そういう切り分けがLINQ的には大事かなって。
static IEnumerable<string> EnumerateFriends(string screenName) { var cursor = "-1"; // 初期値は-1から while (cursor != "0") // 0が出たら終了 { var url = string.Format("http://api.twitter.com/1/statuses/friends.json?screen_name={0}&cursor={1}", screenName, cursor); using (var stream = new WebClient().OpenRead(url)) { var json = DynamicJson.Parse(stream); // 面倒くさいんでDynamicJson使いますよ:) foreach (var item in json.users) { yield return item.screen_name; // screen_nameを列挙 } cursor = json.next_cursor_str; // 次のカーソルにセット } } } static void Main() { var friends = EnumerateFriends("neuecc").ToArray(); }
すっきりと書けるのが分かると思います。え、これだとブロックしてしまって良くない?その通り。じゃあ非同期にしましょう。いえ、Reactive Extensionsで簡単にできてしまいます。yield returnで生成されたEnumerableをObservableに変換するのは、ToObservableです。
static void Main() { EnumerateFriends("neuecc") .ToObservable(Scheduler.ThreadPool) // ThreadPoolで実行! .Subscribe(Console.WriteLine); Console.ReadLine(); // 終了してしまうからね }
ToObservableはデフォルトでは Scheduler.CurrentThread 上で実行されるため、同期的にブロックしますが(※Push型シーケンスだからといって必ずしも非同期とは限らない)、任意のものに変更することも可能です。今回はScheduler.ThreadPoolを指定したので、ThreadPool上で動くようになっています。そのため、ブロックされません。
こういった書き方のほうが、コードがクリアになるし、IEnumerable<T>とIObservable<T>に両対応できてる、という柔軟性の点でも良いかと思います。また、BackgroundWorkerを使うよりも遥かに簡単ですよね。プログレス通知もなく、ただ処理をバックグラウンドでやりたい、というだけならば、Rxを使ったほうが楽チンです。プログレスが必要な場合は、Rxだとその辺の処理を作りこまなければならないので、素直にBackgroundWorkerを用いるのもいいかもしれません。私だったらRxをちょっと拡張してプログレス処理を作り込むほうを選ぶかな?その辺の話はReactive ExtensionsとAsync CTPでの非同期のキャンセル・プログレス処理を参照ください。
また、Observable化するとPublishによる分配 - C#とLinq to JsonとTwitterのChirpUserStreamsとReactive Extensionsなど、色々と応用な使い方が広がるのもメリットの一つと言えるでしょう。
Async CTP
今回は例がWPFなため、WebClientで同期的(OpenRead)に取ってしまいましたし、それでも全然問題ないわけですが、SilverlightとかWP7だったらこの手(同期でJSON取ってきてyield returnで返す)は使えません。同期のOpenReadがそもそもなくて、非同期のOpenReadAsyncしかないからね。どうしましょう?それだとyield returnが使えないのはモチロンのこと、Generateでもうまく動きません。もしページ番号がカーソルのように不定ではなく1,2,3…といった形で辿れたとしても、Observable.Rangeでやると、うまくいきません。非同期なので結果が帰ってくる時間が不定だからです。結果を取得してから次の結果を取得する、という形式にしないとダメなのです。
ところでそもそも、同期的に書いたとしても、本来は書くのは大変なはずなのです。それが、yield returnというコンパイラ生成があるから簡単に書ける。ということは、そうです、非同期もコンパイラ生成してしまえばいいのです、ということでAsync CTPで書きましょう。Async CTPはC# 5.0で入る、かもしれない、async/await構文を使えるようにするためのものです。コミュニティテクノロジープレビュー。ようするにベータ版ですね。
// このコードはAsync CTP (SP1 Refresh)によるもので、将来的にも同じコードで動作することは保証しません async static Task<List<string>> EnumerateFriends(string screenName) { var list = new List<string>(); var cursor = "-1"; while (cursor != "0") { var url = string.Format("http://api.twitter.com/1/statuses/friends.json?screen_name={0}&cursor={1}", screenName, cursor); using (var stream = await new WebClient().OpenReadTaskAsync(url)) // await! { var json = DynamicJson.Parse(stream); foreach (var item in json.users) { list.Add(item.screen_name); // yield returnの代わりに…… } cursor = json.next_cursor_str; } } return list; }
Async CTPは簡単に解説すると、awaitキーワードを使うと、本来非同期のものが同期のように書けるようになります。詳しくは非同期処理 (C# によるプログラミング入門)を参照のこと。コード的にも見たように、差異はWebClientのOpenReadの部分を、await OpenReadTaskAsyncに変更しただけで、あとはまるっきり一緒です。非同期なんて簡単なものだね。と、言いたかったのですが、全部読み込んでListで返してるぢゃん……。これじゃEnumerateじゃないよ、yield returnじゃないの?これだと結果取得に時間かかるし、Takeなどを用いて、途中で止めることもできないし。あまりよくない。
結論としては今のところどうやら無理ということで。asyncの返すものはTaskもしくはTask<T>でなければならない。いや、Task<IEnumerable<T>>を返してくれればいいぢゃん、await yield returnとか出来たら素敵ぢゃないのん?と思わなくもないというか、普通にそういうリクエストも上がっているのですが、それにはIAsyncEnumerable<T>のようなものと、それに対するコンパイラサポートが必要だよね、という返しでした。
Rx + Async
IAsyncEnumerable<T>、それってIObservable<T>で代替出来る話だよね。IObservable<T>は連続的な非同期を内包しているから。(※IObservable<T>は一つのインターフェイスであまりにも多くのものを表現出来てしまい、内部の状態が読みづらく(同期なのか非同期なのか、遅延なのか即時なのか)混乱を生みがちという問題もありますが……)。なので、Rxでやってみましょう。といっても、Rxで完全に自前でやるのは相当大変なので、Async CTPのサポートも併用します。これにより非同期の待機が同期的に書けるようになり、yield returnであったりlist.Addであったりの部分を、OnNextに置き換えるだけになります。
Stable版のRxにはAsync CTP連携は入っていないのですが、Experimental(実験的)版には、awaitで待機出来る、というだけはなく、幾つかAsync CTPと連携できるメソッドが入っています。
// ExperimentalのRxのため、将来的にもこのコードが動作し続けることは保証しません static IObservable<string> EnumerateFriends(string screenName) { // ラムダ式の中でasync書けることがポイント return Observable.Create<string>(async (observer, cancelToken) => { try { var cursor = "-1"; while (cursor != "0") { if (cancelToken.IsCancellationRequested) return; // cancelをチェック var url = string.Format("http://api.twitter.com/1/statuses/friends.json?screen_name={0}&cursor={1}", screenName, cursor); using (var stream = await new WebClient().OpenReadTaskAsync(url)) // await! { var json = DynamicJson.Parse(stream); foreach (var item in json.users) { observer.OnNext(item.screen_name); // yield returnのかわりに } cursor = json.next_cursor_str; } } } catch (Exception ex) { observer.OnError(ex); return; // 例外発生時はOnErrorを呼んで終了 } observer.OnCompleted(); // 例外発生もキャンセルもなく完了したなら、OnCompletedを呼ぶ }); } static void Main() { EnumerateFriends("neuecc") .Take(350) // 350件後にDisposeされてtokenがcancelになる .Subscribe( s => Console.WriteLine(s), e => Console.WriteLine("error:" + e), () => Console.WriteLine("完了")); // Takeのほうから呼び出されるので、cancel扱いになっても表示される Console.ReadLine(); }
Observable.Create(RangeやGenerateなどの生成子、WhereやSelectなどの演算子の全てが使っている、本当のプリミティブの生成子)を使って、生のobserverでOnNext, OnError, OnCompletedの3つを制御してやります。Createやtry-catchの分、ネストが深くなってしまっていますが、コード自体は同期的に、yield returnを使って書いていたものとほとんど変わってないのが分かると思います。yield returnの部分にOnNextを置いた、それだけでそのまま置き換えられています。
これならIObservable<T>でも十分に自動生成のサポートが効いていると言えなくもないですね。やってみて、結構満足できてしまった。パフォーマンス的にも、演算子をベタベタ組み合わせるのはあまり良くはならないので、こうしてasync/awaitと連携させて作れると、素直に書けるうえに、パフォーマンス向上も狙えるのが嬉しい。ただ、OnErrorやOnCompleted、キャンセル(Dispose)をどうするか。考慮する事項が多いので、ある程度分かっていないと大変かもしれません。全て考えておかないと、正しく動作しません。既存演算子の組み合わせだけで済ませられるなら、そういった考慮事項は演算子が受け持ってくれるので、考えなくて済むのですが……。どうしても演算子の組み合わせじゃうまく出来ない、逆に複雑になりすぎる、そういった時の奥の手、ぐらいに考えておくと良さそう。
ところでasyncはメソッドの宣言だけでなく、ラムダ式の部分でも宣言できてawaitすることが出てきてしまうんですよね、ならば、同じようなコンパイラ生成であるyield returnも、現状は外部メソッドでしか使えないわけですが、以下のようにインラインでも使えるようになってくれると嬉しいなって。思ってしまうのです。
// 妄想なので、現状はこれは出来ませんが! var infinity = Enumerable.Create(()=> { var num = 0; while(true) { yield return num++; } });
そんなもの散々突っ込み受けたですって?Iterator Blocks Part Seven: Why no anonymous iterators? - Fabulous Adventures In Coding。ええ、知ってます。しかし、awaitなどで必要さの要請を受けて、コストとベネフィットが逆転するときが来た、と、思うのです。それに、VBでも、いや、なんでもない。
Expand
さて、ともかくAsync CTPは未来の話であり、現状手元にあるもので何とかする方法はないのだろうかというと、あります。ようするところ、再帰的に辿ってるわけですよね、cursorを。じゃあ、Expandです。ExpandはReactive Extensions v1.0安定版リリースでEnumerableバージョンのものを説明しましたが、Observableバージョンもあります。
// 補助メソッド、Async CTPにはOpenReadTaskAsyncとか、そういうのがデフォで用意されてますが、 // Rxにはないので、自前で用意しなきゃあいけないという、それだけの話です(ダウンロードしてストリングを返すだけのもの) public static class WebRequestExtensions { public static IObservable<string> DownloadStringAsync(this WebRequest request) { return Observable.Defer(() => Observable.FromAsyncPattern<WebResponse>( request.BeginGetResponse, request.EndGetResponse)()) .Select(res => { using (var stream = res.GetResponseStream()) using (var sr = new StreamReader(stream)) { return sr.ReadToEnd(); } }); } } // ExpandはStable版にはまだ搭載されていないので、Experimental版を使ってください static IObservable<string> EnumerateFriends(string screenName) { Func<string, IObservable<dynamic>> downloadJson = cursor => { var url = string.Format("http://api.twitter.com/1/statuses/friends.json?screen_name={0}&cursor={1}", screenName, cursor); return WebRequest.Create(url).DownloadStringAsync().Select(DynamicJson.Parse); }; return downloadJson("-1") .Expand(d => (d.next_cursor_str == "0") ? Observable.Empty<dynamic>() // TakeWhileで判定すると最後の一つを取りこぼすので : downloadJson(d.next_cursor_str)) .SelectMany(d => (dynamic[])d.users) .Select(d => (string)d.screen_name); }
そこそこ直感的ではないでしょうか?最初 Expand().TakeWhile(next_cursor_str != “0″) と書いたのですが、それだと最後のページを取りこぼしてしまうのに気づいて、Emptyを投げる方針に変更しました。その辺、境界については注意を払わなきゃですね。
そして、残念ながら、ExpandはRxのStable版にはまだない。ということはWP7にもないわけで。
再帰とRx
Stable版でもやりましょう。awaitなし、Expandなし。では、どうやって作りましょうか。うーん、再帰的というのなら、本当に再帰させてしまえばいいのではないか?
static IObservable<string> EnumerateFriends(string screenName) { Func<string, IObservable<dynamic>> downloadJson = null; // 再帰するにはこーして最初にnull代入 downloadJson = cursor => { var url = string.Format("http://api.twitter.com/1/statuses/friends.json?screen_name={0}&cursor={1}", screenName, cursor); return WebRequest.Create(url) .DownloadStringAsync() .Select(DynamicJson.Parse) // ここまでExpandと共通 .SelectMany(json => { // Expandメソッドの中でやってることを大幅簡易化、ということです、つまるところ。 var next = (json.next_cursor_str == "0") ? Observable.Empty<dynamic>() : downloadJson((string)json.next_cursor_str); return (IObservable<dynamic>)Observable.StartWith(next, json); }); }; return downloadJson("-1") .SelectMany(d => (dynamic[])d.users) // ここからもExpandと共通 .Select(d => (string)d.screen_name); }
これを再帰というには、あまり再帰してないのですが、まあ雰囲気雰囲気。Expandと大体共通です。つまるところ、Expandを自前で作る、のは結構大変なので、Expandのように汎用的ではなく、特化したものをその場で作る、といった程度の代物。そうすれば、少しは簡単に用意できます。
まとめ
同期でListに格納するだけなら簡単。遅延でやるのもyield returnのお陰で簡単。非同期で辿るのは難しい。awaitで複数の値をyield的に列挙するのは現状難しい。Rxとawaitの連携は大変素晴らしい。Expandは便利。なければないで何とかなる。でもやっぱ大変。
一見簡単なことが存外難しいってのはいくないですね。一見簡単なら、簡単なままでできないと。Expandも悪くはないんですけど、中々どうして慣れてないと分かりづらい。しかし、将来のC#には十分期待できそう。と、思いました、まる。あとRxとAsyncは全然仲良しなんですよ~、というところです。
Rxでのイベント変換まとめ - FromEvent vs FromEventPattern
- C# Rx WindowsPhone7 - 11.07/06
Reactive Extensionsの機能の一つに.NETにおけるイベントをIObservable<T>に変換する、というものがあります。Bridging with Existing .NET Events。そして、そのためのメソッドがFromEventでした。ところが最近のRxでは二つ、FromEventとFromEventPatternが用意されています。この差異は何なのでしょうか?
結論としては、過去のRx(このサイトの古い記事や他のサイトの過去の記事などで触れられている)やWindows Phone 7でのFromEventはFromEventPatternに改名されました。後続にEventPatternという(object Sender, TEventArgs EventArgs)を持つ.NETのイベントの引数そのものを渡すものです。そして、空席になったFromEventに新しく追加されたFromEvent(紛らわしい!)は、EventArgsだけを送ります。それ以外の差異はありません。
つまるところFromEventは FromEventPattern.Select(e => e.EventArgs) ということになります。なら、それでいいぢゃん、何も混乱を生む(WP7のFromEventがFromEventPatternである、というのは致命的よねえ)ことはないよ、とは思うのですが、パフォーマンスの問題でしょうかね。確かに、Senderは必要なく使うのはEventArgsだけの場合が多い。それなのに、毎回EventPatternを生成していたり、Selectというメソッド呼び出しが入るのは無駄です。
そもそもインスタンスに対してFromEventで包むということは、クロージャでsenderは変数としていつでもどこでも使えてしまうのですよね、そもそも、そもそも。そういう意味でも送られてくるのはEventArgsだけでいいのであった。というわけで、基本的にはFromEventでいいと思います。
FromEventPatternについて
では、改めてFromEventPatternを復習します(WP7の人はFromEventで考えてください)。Observable.FromEventPattern(TEventArgs) Method (Object, String) (System.Reactive.Linq)にサンプルコードがあるのですけれどね。そうそう、MSDNのリファレンスには、一部のメソッド/一部のオーバーロードにはサンプルコードがあります。全部ではないのがミソです、見て回って発掘しましょう。まあ、というわけで、とりあえずそのFileSystemWatcherで。
// FileSystemWatcherは指定フォルダを監視して、変化があった場合にイベントを通知します // 例えばCreatedイベントはファイルが作成されたらイベントが通知されます var fsw = new FileSystemWatcher(@"C:\", "*.*") { EnableRaisingEvents = true }; // FromEventPatternその1、文字列でイベント名指定 Observable.FromEventPattern<FileSystemEventArgs>(fsw, "Created") .Subscribe(e => Console.WriteLine(e.EventArgs.FullPath)); // FromEventPatternその2、静的なイベントをイベント名指定(WP7にはない) Observable.FromEventPattern<ConsoleCancelEventArgs>(typeof(Console), "CancelKeyPress") .Subscribe(e => Console.WriteLine(e.EventArgs.SpecialKey));
一番馴染み深いと思うのですが、文字列でイベント名を指定するものです。その2のほうはあまり見ないかもしれませんが、静的イベントに対しての指定も可能です。これら文字列指定によるメリットは、比較的シンプルであること。デメリットは、リフレクションを使うので若干遅い・スペルミスへの静的チェックが効かない・リファクタリングが効かない、といった、リフレクション系のデメリットそのものとなります。
リフレクションしかないの?というと、勿論そんなことはありません。
// FromEventPatternその3、EventHandlerに対する変換 var current = AppDomain.CurrentDomain; Observable.FromEventPattern(h => current.ProcessExit += h, h => current.ProcessExit -= h) .Subscribe(e => Console.WriteLine(e.EventArgs)); // FromEventPatternその4、EventHandler<T>に対する変換 Observable.FromEventPattern<ContractFailedEventArgs>( h => Contract.ContractFailed += h, h => Contract.ContractFailed -= h) .Subscribe(e => Console.WriteLine(e.EventArgs.Message)); // FromEventPatternその5、独自イベントハンドラに対する変換 Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>( h => new FileSystemEventHandler(h), h => fsw.Created += h, h => fsw.Created -= h) .Subscribe(e => Console.WriteLine(e.EventArgs.FullPath));
イベントの登録と削除を行うためのラムダ式を渡してやります。その3とその4は比較的分かりやすいのではないでしょうか。その5の第一引数が謎いのですが、これはconversionです。C#の型システムの都合上、そのまんまだと独自イベントハンドラを処理出来ないので、型を変換してやる必要があるという定型句。
数あるFromEventPatternのオーバーロードの中で、一番多く使うのはその5だと思います。何故なら、C#のイベントは独自イベントハンドラになっていることが多いから。はっきしいって、最低です。EventHandler<T>を使ってくれてさえいれば、こんな苦労はしなくて済むというのに。独自イベントハンドラは100害あって一利なし。え、WPFとか.NET標準がイベントハンドラは独自のものを使ってる?それは、WPFが悪い、.NET設計の黒歴史、悪しき伝統。
それと、もはや独自デリゲートも最低です。FuncやActionを使いましょう。C#のデリゲートはメソッドの引数や戻り値が一致していようが、型が違ったら別のものとして扱われます。そのことによる不都合は、↑で見たように、あるんです。極力ジェネリックデリゲートを使いましょう。そうすれば、こんな腐った目に合わなくても済みます。
ところで、その5は、もう少しだけ記述が短くなります。
// FromEventPatternその5、第一引数別解、こう書くと短くて素敵 Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>( h => h.Invoke, h => fsw.Created += h, h => fsw.Created -= h) .Subscribe(e => Console.WriteLine(e.EventArgs.FullPath));
h.Invoke。というのは、割とhそのものなわけですが、しかしInvokeと書くことで型が変換されます。この辺はコンパイラの都合上のマジックというか何というか。そういうものだと思えばいいのではかと。その5のスタイルで書くときは、この書き方をすると良いと思います。で、まだオーバーロードがあって
// その6 Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>( h => fsw.Created += h, h => fsw.Created -= h) .Subscribe(e => Console.WriteLine(e.EventArgs.FullPath));
conversionが不要で書けたりもします。一見素晴らしい、のですが、これ、中でなにやってるかというとconversionに相当するものをリフレクションで生成してるだけだったりして。そのため、なるべくconversionを使うオーバーロードのほうを使ったほうがよいでしょう。h => h.Invokeを書くだけですしね。このオーバーロードは紛らわしいだけで存在意義が不明すぎる。
FromEventについて
と、長々と見てきましたが、ではFromEventのほうも。
// FromEvent Observable.FromEvent<FileSystemEventHandler, FileSystemEventArgs>( h => (sender, e) => h(e), h => fsw.Created += h, h => fsw.Created -= h) .Subscribe(e => Console.WriteLine(e.FullPath)); // FromEventPatternその5(比較用) Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>( h => (sender, e) => h(sender, e), h => fsw.Created += h, h => fsw.Created -= h) .Select(e => e.EventArgs) .Subscribe(e => Console.WriteLine(e.FullPath));
というわけで、FromEventPatternのその5に近いわけですが、conversionでEventArgsしか渡していない、という点が差異ですね。なので、後続にはsenderが伝わってこず、EventArgsしか通りません。まあ、senderは、↑の例ですとfswでどこでも使えるので、そもそも不要なわけで、これで良いかと思います。
ところでFromEventも色々なオーバーロードがあるにはあるんですが、私の頭では存在意義が理解できなかったので無視します。挙動とかは理解したんですが、なんというか、存在する必要性、有効な利用法がさっぱり分からなかったのです……。まあ、多分、あんま意味ないと思うので気にしないでもいいかと。
拡張メソッドに退避させよう
FromEventにせよFromEventPatternにせよ、長いです。長い上に定型句です。なので、拡張メソッドに退避させると、スッキリします。例えば、今まで見てきたFileSystemWatcherだったら
// .NETのFromEventなら IObservable<TEventArgs> // .NETのFromEventPatternなら IObservable<EventPattern<TEventArgs>> // WP7のFromEventなら IObservable<IEvent<TEventArgs>> // を返す拡張メソッド群を用意する。 // 命名規則はイベント名AsObservableがIntelliSenseの順序的にお薦め public static class FileSystemWatcherExtensions { public static IObservable<FileSystemEventArgs> CreatedAsObservable(this FileSystemWatcher watcher) { return Observable.FromEvent<FileSystemEventHandler, FileSystemEventArgs>( h => (sender, e) => h(e), h => watcher.Created += h, h => watcher.Created -= h); } public static IObservable<FileSystemEventArgs> DeletedAsObservable(this FileSystemWatcher watcher) { return Observable.FromEvent<FileSystemEventHandler, FileSystemEventArgs>( h => (sender, e) => h(e), h => watcher.Deleted += h, h => watcher.Deleted -= h); } public static IObservable<RenamedEventArgs> RenamedAsObservable(this FileSystemWatcher watcher) { return Observable.FromEvent<RenamedEventHandler, RenamedEventArgs>( h => (sender, e) => h(e), h => watcher.Renamed += h, h => watcher.Renamed -= h); } public static IObservable<FileSystemEventArgs> ChangedAsObservable(this FileSystemWatcher watcher) { return Observable.FromEvent<FileSystemEventHandler, FileSystemEventArgs>( h => (sender, e) => h(e), h => watcher.Changed += h, h => watcher.Changed -= h); } }
var fsw = new FileSystemWatcher(@"C:\", "*.*") { EnableRaisingEvents = true }; // 例えば、ただ変更をロギングしたいだけなんだよ、という場合の結合 // FromEventを外出ししていることによって、すっきり書ける Observable.Merge( fsw.CreatedAsObservable(), fsw.DeletedAsObservable(), fsw.ChangedAsObservable(), fsw.RenamedAsObservable()) .Subscribe(e => Console.WriteLine(e.ChangeType + ":" + e.Name));
といった形です。また、普通に+-でのイベント以外のものへの登録も可能です。例えば
// LayoutRootはWPFの一番外枠の<Grid Name="LayoutRoot">ということで。 Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>( h => (sender, e) => h(e), h => LayoutRoot.AddHandler(UIElement.MouseDownEvent, h), h => LayoutRoot.RemoveHandler(UIElement.MouseDownEvent, h)) .Subscribe(e => Debug.WriteLine(e.ClickCount));
こんな形のものもObservable化が可能です。
イベントの解除
Subscribeの戻り値はIDisposableで、Disposeを呼ぶことでイベントが解除されます。
// アタッチ var events = Observable.Merge( fsw.CreatedAsObservable(), fsw.DeletedAsObservable(), fsw.ChangedAsObservable(), fsw.RenamedAsObservable()) .Subscribe(e => Console.WriteLine(e.ChangeType + ":" + e.Name)); // デタッチ(合成などをしていて、元ソースが複数ある場合も、すべて解除されます) events.Dispose();
Rxのこの仕組みは、従来に比べて圧倒的にイベントの解除がやりやすくなっていると思います。
まとめ
非同期の説明ばかりしてきていて、イベントはすっかり置き去りだったことを、まずはゴメンナサイ。少し前からFromEvent周りは大きな仕様変更が入ったわけですが、ようやくまともに解説できました。基本中のキの部分であるここが、過去のリソースがそのまま適用出来ないという最悪の自体に陥っていたので、とりあえずこれで何とか、でしょうかどうでしょうか。
小さなこととはいえ、WP7との互換性が絶えているのが痛いのですが、その辺どうにかならなかったのかねー、とは思います。けれど、このEventArgsだけ送るFromEvent自体は良いと思います。 .Select(e => e.EventArgs) が定型句だったので、こういった変更は喜ばしい限り。それと、今まで思っていた、ぶっちゃけラムダ式とかRxでイベント登録するならsenderって不要じゃね?に対する答え(その通りで、完全に不要)を出してくれたのが嬉しい。
さて、変換できるのはいいけれど、じゃあどこで使うのがいいの?という話がいつもありません。次回は、時間周りと絡めて、その辺のお話が出来ればと思いますが、いつも次回予告が達成されたことはないので、別のことを書くでしょう←ダメぢゃん。
Reactive Extensions v1.0安定版リリース
Reactive Extensions v1.0 Stable and v1.1 Experimental available now! ということで、今までも安定版だの正式だの何なり言っていましたが、今回こそ、本当に本当に正式リリース、v1.0だそうです。整理されたドキュメント、多くのチュートリアルビデオ、大幅なパフォーマンス改善、そして、よくテストされた(かどうかは不明)安定版としてのライブラリ本体。全てが整いました。さあ、使いましょう!実際のプロダクトに!
Announcing the Official Release of Rx! | Charles | Channel 9
下のリンク先にあるRx Workshopで、沢山のビデオで学ぶことが出来ます。MSDN - .NET Development - Reactive Extensions
MSDNでのドキュメント。コンセプトから細かい使い方まで、しっかり書かれています。Reactive Extensions Class Library Reference
MSDNでのクラスライブラリリファレンス。
また、NuGetではRx-でStable版を、Rx_experimental-で実験版を、他にIx_experimental-でIxをインストールすることが可能です。
Ix復活
Ix(Interactive Extensions、Reactiveの反対ということでEnumerableEx、Linq to Objectsを拡張する拡張メソッド群)が復活しました。復活前に、今後についての意見を募集していたのですが、素敵なことに私の意見が全部反映されていました!XxxEnumerableはIntelliSenseの邪魔だから廃止してね、とかForEachにはindex付きのオーバーロード入れてよ、とかTreeを走査するメソッド入れてよ、とか。これは嬉しい。いやあ、やっぱり言っておくものですね。
そんなわけで、もうForEachを自作する必要はありません:)
標準クエリ演算子で何が「出来ない」のかを知っておくことは大切です。標準では複数回列挙なしで前後の値を使うことは出来ないし、再帰的に辿るような、複数段のSelectManyも出来ない。MaxByなどキーを使った最大値の取得もない。
Ixは、そこを補完してくれます。
Expand
Ixの中から、Expandを紹介します。なお、Rx(Observable)にはExperimental Releaseのほうにはあるのですが、まだStableには入っていません。
これは何かというと幅優先探索でツリーを解きほぐします。イミワカリマセンネ。ええと、ツリー状のオブジェクトを辿る場合は、通常は再帰を使って書くと思います。でも、そうして再帰で辿るのって、各枝の値が欲しいわけなんですよね?もしそうなら、ツリーは一直線上に出来る。IEnumerable<T>に出来る。LINQが適用できる。
ええ。熟練した C# 使いは再帰を書かないのです。で、ツリーです。ツリーを辿るのは頻出の再帰構造でパターン化できて、うんたらかんたら。ああ、もう!与えられた木から,子→親への対応を作る,を C# で - NyaRuRuの日記を見るといいです、それで全部解決です!
ExpandはBreadthFirstにあたります。DepthFirstはないの?というと、今のところないですねー。ないものはないです、しょうがない。それはさておき、このツリー的なものを辿るのというのは割とあるシチュエーションで、そしてExpandというメソッドは実に強力なので、是非使い方をマスターして欲しい。ので、例を出します。例えばWinFormsのコントロール。
Panelの下にButtonや、子Panelが並んでいます。さて、この中から全てのButtonを取り出したいのですが、どうしましょうか?予め配列にButtonを持っておく、のもまあ答えですが、ルート階層から辿るようにしましょう。Formに並ぶコントロールは、階層に別れたツリー構造をしています。Expandを使ってみましょう。
Expandはセレクターの結果がEmptyになるまで、辿り続けます。辿る順番は階層順(幅優先)。今回はContorolsを辿って全てのControlを列挙、Buttonが欲しいのでOfTypeでフィルタリング。というわけです。WPFやSilverlightでも、同様に辿ることが出来ます(ちょっとWPFのコントロール階層が面倒くさくて、コードがゴチャゴチャするので、今回はWinFormsを例とさせていただきました)
こういった走査メソッドはlinq.jsにもあります(CascadeBreadthFirst、メソッド名のとおり、NyaRuRuさんの作成されたAchiralから大きな影響を受けています)。JavaScriptの場合、ツリーの代表的なものはDOMです、というわけで勿論DOMに適用できるのですが、DOMの列挙はjQueryでやったほうがいいですよー、なので、JSONやオブジェクト(これもまたツリーになっている)の走査に使うのがいいかもしれません。何にせよ、使いどころというのは存外あるものです。
気になった点
そんな素敵なIxですが、触っていて幾つか気になった点があったので、またForumに投げておきました。リクエストが反映されたことで、調子に乗って味を占めているのかもしれません。ではなくて、フィードバックは積極的に出してあげたほうがいいでしょう常識的に考えて。英語?全部機械翻訳です、はは、まあ、コードがあれば伝わるはず。伝わりました。返答が15分で来た。
まず、Scanの挙動。seedなしの場合に、Rxは最初の値も列挙しますが、Ixは最初の値をスルーするという、挙動の違い。で、これ、最初の値がスルーされるのは都合が悪いので(スルーしたきゃあSkip(1)すればいい)、修正かなあ、と思います。返答では、この点については何も言ってませんでしたが。
Scanにはもう一つ、seed有りの際に、Rx, Ixともにseedをスルーしますが、F#などはseedも列挙します。これは以前はScan0というseed含めて列挙する別のメソッドがあった(WP7版にはある)のですが、今はScan0は廃止されたので、それならScanの挙動をseed込みでの列挙に変更すべき。と、思ったのですが、返答はStartWith(seed)を使えばいいとのこと。それでも確かにいいのですが、基本seed有りにしてseed飛ばしたい時はSkip(1)のほうが使いやすいと思うのだけど。まあ、これはWP7版との互換性の問題もありますし、そもそもRxはStableと言った以上、もう挙動は変えられないので、しょうがないところかもしれません。
他にはRepeat拡張メソッド(無限リピートする)のソースがEmptyの場合の挙動。例えば Range(1,1).Repeat().Take(3) と1,1,1になるわけですが、 Empty
最後にちょっとリクエストしてみた。今回のIxではDistinctにオーバーロードが足されています。通常だとIEqualityComparer<T>というダルいものをクラス定義して(ここが最悪!)渡さなければいけないのですが、ラムダ式でキーを指定するだけで済みます。言うならばDistinctByといったところ。これは、実に大変有益です。このオーバーロードは、AnonymousComparerという私が以前に作ったものにも載っているのですが(ちなみに拡張メソッドがかち合ってしまうため、Ixと同時使用は不可能になってしまった!まあ、.csファイル一個のライブラリなので、かち合う部分はコメントアウトすればいいのですが)、大変重宝しています。しょっちゅう使ってます。特にExceptとかで多用しているんですが……、今回IxではDistinctにしかオーバーロード足されていません。他の集合系メソッドであるIntersect, Except, Unionでも使えると便利なのになー、って思ってしまうのです。なのでリクエストしておきました。回答は、考えておく、とのことなのでもしかしたら次のバージョンでは乗っかっているかもしれません。
日本語だと言えるけれど、バッサリ切って機械翻訳した英語だと伝えたいニュアンスは吹っ飛んでしまうなあ、ううみぅ。必ずしもコードがあれば伝えられる、わけでもないか、当たり前だけど。
QueryableEx(笑)
ネタ。NuGetではIx_Experimental-Providerで入るんですが、まあ、ネタ。中身はEnumerableExのIQueryable版です。何がネタなのかというと、IQueryableはそれだけでは何の意味もなくて、解釈するエンジン(Linq to SqlなりLinq to Twitterなり)が大事なわけです。そうでなければ、not supportedをぶん投げるだけです。さて、そして、標準クエリ演算子ですらnot supported率が少なくないのに、QueryableExに対応するクエリプロバイダ……。ありえない、です。
そんなわけで、使う機会はないでしょう。んまあ、気合でQueryableExすらもフルサポートするクエリプロバイダを自作すれば、活用出来ますが、やはりそんな機会はないでしょう。
RxJSは?
ドキュメント書いたりとか、QueryableEx作ったり(笑)とか、色々忙しかったのでしょふ。今回「も」全く音沙汰なしですが、次こそは更新されるんじゃないですかねー、分かりませんけど。jQueryにはDeferred乗りましたが、アレは正直かなり使いづらいのでその点でも私はRxJSにしたいなあ。それと、MS支援でのNode.jsのWindows対応も発表されましたし、JavaScriptのAsyncを何とかするためのRx、はかなり価値があると思うので、もう少し頑張って欲しいな、と思います。
まとめ
しっかりとMSDN入りしている、ドキュメントもある、正式にv1 Stableと告知されている、など、もう採用できない理由はなくなりました。日本語リソースはないですが、それは気合で乗り切ればいいぢゃない(とか言ってるうちはダメなのでしょうがー)。
Jesse Liberty(オライリーから出ているプログラミングC#の著者)による解説本も今秋に出るようだし、確実に、順調にメインストリームに乗るテクノロジとしての道を歩んでいますので、安心して追えるのではないかと思います。
ReactiveOAuth ver.0.4 - Twitpic(OAuth Echo)対応
- C# Rx WindowsPhone7 - 11.06/23
ver.0.4になりました。少し前に0.3.0.1をこっそり出していたので、それを含めて0.3からの差分は、「対象Rxのバージョンが現在最新の1.0.10605(Stable)」に、というのと「Realmが含まれていると認証が正しく生成出来なかったバグの修正」と、「TwitpicClientサンプルの追加」になります。バグのほうは本当にすみません……。Twitterでしかテストしてない&TwitterはRealm使わないため、全然気づいていなくて。ダメですねホント。
OAuth Echo
TwitpicはOAuth Echoという仕組みでTwitterと連携した認証をして、画像を投稿できます。詳しくはUsing OAuth Echo | dev.twitter.comやTwitPic Developers - API Documentation - API v2 » uploadにありますが、よくわかりませんね!Twitpicに画像を投稿、というわけでTwitpicのAPIにアクセスするわけですが、その際のヘッダにTwitterに認証するためのOAuthのヘッダを付けておくと、Twitpic側がTwitterに問い合せて認証を行う。という仕組みです、大雑把に言って。
ただのOAuthとはちょっと違うので、今までのReactiveOAuthのOAuthClientクラスは使えない。けれど、認証用ヘッダの生成は同じように作る。というわけで、ここはReactiveOAuthにひっそり用意されているOAuthBaseクラスを継承して、Twitpic専用のTwitpicClientクラスを作りましょう。
が、作るのもまた少し面倒なので Sample/TwitpicClient/TwitpicClient.cs に作成したのを置いておきました。ファイルごとコピペってご自由にお使いください。.NET 4 Client Profile, Silverlight 4, Windows Phone 7の全てに対応しています。
Windows Phone 7でのカメラ撮影+投稿のサンプル
TwitpicClient.cs の解説は後でやりますが、その前に利用例を。WP7でカメラ撮影+投稿をしてみます。CameraCaptureTaskの利用法に関しては CameraCaptureTaskを使ってカメラで静止画撮影を行う – CH3COOH(酢酸)の実験室 を参考にさせて頂きました。TwitterのAccessTokenの取得に関しては、ここでは解説しませんので neue cc - ReactiveOAuth - Windows Phone 7対応のOAuthライブラリ を参照ください。
// CameraCaptureTaskのCompletedイベント void camera_Completed(object sender, PhotoResult e) { if (e.TaskResult == TaskResult.OK) { // 撮影画像(Stream)をバイト配列に格納 var stream = e.ChosenPhoto; var buffer = new byte[stream.Length]; stream.Read(buffer, 0, buffer.Length); // key, secret, tokenは別に設定・取得しておいてね new TwitpicClient(ConsumerKey, ConsumerSecret, accessToken) .UploadPicture(e.OriginalFileName, "from WP7!", buffer) .ObserveOnDispatcher() .Catch((WebException ex) => { MessageBox.Show(new StreamReader(ex.Response.GetResponseStream()).ReadToEnd()); return Observable.Empty<string>(); }) .Subscribe(s => MessageBox.Show(s), ex => MessageBox.Show(ex.ToString())); } }
new TwitpicClient(キー, シークレット, アクセストークン).UploadPicture(ファイル名, メッセージ, 画像) といった風に使います。戻り値はIObservable<string>で結果(投稿後のURLとか)が返ってくるので、あとは好きなように。投稿に失敗した場合は、WebExceptionが投げられるので、それを捉えてエラーメッセージを読み取ると開発には楽になれそうです。
TwitpicClient.cs
以下ソース。Sample/TwitpicClient/TwitpicClient.cs と同じですが、自由にコピペって使ってください。大事なことなので2回言いました。このコード自体はTwitpicに特化してありますが、認証部分のヘッダを少しと画像アップロードを変更する部分を弄れば、他のOAuth Echoサービスにも対応させることができると思います。
using System; using System.Linq; using System.Text; using System.Net; using System.IO; #if WINDOWS_PHONE using Microsoft.Phone.Reactive; #else using System.Reactive.Linq; #endif namespace Codeplex.OAuth { public class TwitpicClient : OAuthBase { const string ApiKey = ""; // set your apikey readonly AccessToken accessToken; public TwitpicClient(string consumerKey, string consumerSecret, AccessToken accessToken) : base(consumerKey, consumerSecret) { this.accessToken = accessToken; } private WebRequest CreateRequest(string url) { const string ServiceProvider = "https://api.twitter.com/1/account/verify_credentials.json"; const string Realm = "http://api.twitter.com/"; var req = WebRequest.Create(url); // generate oauth signature and parameters var parameters = ConstructBasicParameters(ServiceProvider, MethodType.Get, accessToken); // make auth header string var authHeader = BuildAuthorizationHeader(new[] { new Parameter("Realm", Realm) }.Concat(parameters)); // set authenticate headers req.Headers["X-Verify-Credentials-Authorization"] = authHeader; req.Headers["X-Auth-Service-Provider"] = ServiceProvider; return req; } public IObservable<string> UploadPicture(string filename, string message, byte[] file) { var req = CreateRequest("http://api.twitpic.com/2/upload.xml"); // choose xml or json req.Method = "POST"; var boundaryKey = Guid.NewGuid().ToString(); var boundary = "--" + boundaryKey; req.ContentType = "multipart/form-data; boundary=" + boundaryKey; return Observable.Defer(() => Observable.FromAsyncPattern<Stream>(req.BeginGetRequestStream, req.EndGetRequestStream)()) .Do(stream => { using (stream) using (var sw = new StreamWriter(stream, new UTF8Encoding(false))) { sw.WriteLine(boundary); sw.WriteLine("Content-Disposition: form-data; name=\"key\""); sw.WriteLine(); sw.WriteLine(ApiKey); sw.WriteLine(boundary); sw.WriteLine("Content-Disposition: form-data; name=\"message\""); sw.WriteLine(); sw.WriteLine(message); sw.WriteLine(boundary); sw.WriteLine("Content-Disposition: form-data; name=\"media\"; filename=\"" + filename + "\""); sw.WriteLine("Content-Type: application/octet-stream"); sw.WriteLine("Content-Transfer-Encoding: binary"); sw.WriteLine(); sw.Flush(); stream.Write(file, 0, file.Length); stream.Flush(); sw.WriteLine(); sw.WriteLine("--" + boundaryKey + "--"); sw.Flush(); } }) .SelectMany(_ => Observable.FromAsyncPattern<WebResponse>(req.BeginGetResponse, req.EndGetResponse)()) .Select(res => { using (res) using (var stream = res.GetResponseStream()) using (var sr = new StreamReader(stream, Encoding.UTF8)) { return sr.ReadToEnd(); } }); } } }
認証ヘッダ作成はConstructBasicParametersとBuildAuthorizationHeaderというprotectedメソッドで行います。わけわかんないよね…気持ち悪いよね…。使いにくいメソッドです、すみません、私もそう思います。そういうものだと思って、見ないふりしてもらえれば幸いです。
コードの大半を占めているのは画像を投稿するためのmultipart/form-dataのもので、これはもうOAuth Echo関係ない話、で、面倒ぃ。特にWP7での非同期だと涙が出る。POSTはBeginGetRequestStreamとBeginGetResponseの二つの非同期メソッドをセットで使う必要があるため、コードがごちゃごちゃするのです。
しかしReactive Extensionsを使えばあら不思議!でもないですが、ネストがなくなって完全に平らなので、結構普通に読めるのではないでしょうか?(ストリーム書き込みのコード量が多いのは、これは同期でやっても同じ話なので)。例外処理も利用例のところで見たように、Catchメソッドをくっつけるだけ。実に色々とスッキリします。
Rxがあれば非同期POSTも怖くない。
やっていることは単純で、FromAsyncPatternでBegin-Endを変換。StreamへのWriteは後続への射影はなく、対象(Stream)に対しての副作用(書き込み)のみなのでDo、RequestStream->Responseへの切り替えはSelectMany、Responseから結果のStringへの変換はSelect、と、お決まりの定型メソッドに置き換えていっただけです。この辺はパターンみたいなものなので、これやるにはこのメソッドね、というのを覚えてしまえばそれでお終いです。
Stream読み書きは非同期にしないの?
StreamにもBeginReadとかBeginWriteとかありますものね。しかし、しません(キリッ。理由は死ぬほど面倒だからです。やってみると分かりますが想像以上に大変で、おまけに何とか実現するためにはRxでのチェーンを大量に重ねる必要がありオーバーヘッドがバカにならない……。なので、わざわざやるメリットも全くありません。
一応、ReactiveOAuthのOAuthClientは、そこも非同期でやってますが、わざわざ頑張った意味があったかは、かなり微妙なところ。実装は Internal/AsynchronousExtensions.cs にあるので参照ください。それと、この AsynchronousExtensions.cs はReactive Extensionsで非同期処理を簡単にで言った「拡張メソッドのすゝめ」を実践したものでもあります。WebRequestはプリミティブすぎて扱い難いので、Rxに特化したうえで簡単に扱えるようにDownloadStringやUploadValueなどといったメソッドを拡張してあります。便利だと思いますので、こちらも TwitpicClient.cs と同様に、ファイルごと自由にコピペって使ってやってください。
まとめ
ReactiveOAuthを公開する目的に、「これが入り口になってRxの世界を知ってもらえると嬉しい」というのもあったのですが、WP7開発で利用してもらったりと、その目的は少しは達成出来たかもで、良かった良かった。ちょっと練りたりなかったり、未だにバグがあったり(本当にごめんなさい!)と至らない点も多いですが、今後も改善していきますのでよろしくお願いします。
MSDNの上のReactive Extensions
DevLabsを卒業し、晴れて正式にData Developer Center入りしたReactive Extensionsですが、徐々に正式リリースへ向かう準備として、ついにドキュメントがMSDN入りを果たしました。
まだPre-releaseということで工事中の部分が多いですが、これはドキドキしますね。Getting Started with Rxはどんなところで使えるかの説明とダウンロード先について、Using Rxは詳細なドキュメント(まだ工事中項目も幾つか、でもかなり充実している感)、Reactive Extensions Class Libraryはリファレンス。必要十分には揃っていますね、あとは大きめのサンプルも欲しいところだけど、追加されるかな?
ところで、ツリー階層を見てください。
.NET Development直下です。何て素晴らしい位置にあるのか!Entity FrameworkやSilverlight などと同列に並べられているのを見ると、ついに始まったな、と感無量です。
その他のRx
Rx本体が順風満帆なのに比べると、RxJSとIx(EnumerableEx)はどうしちゃったのでしょう……。まず、RxJSは、やる気は一応あるみたいです。ダウンロードも可能ですしね。APIは随分更新されていなくて、.NET版Rxとかなり差が開いてしまいましたが(バグも幾つか見つかっているのですが)。今はRxチーム自体がドキュメント周りで忙しいということで手を付けてられないっぽく、ゆっくり待つしかないです。プロジェクト自体は死んでいないそうなので、そこだけは安心してもいいかと。(私は内心不安ですが!)
Ixのほうは、一旦死亡です。ダウンロードセンターはもとより、NuGetからも、もう手に入らなくなりました。が、しかし、完全終了ではなく、一旦引っ込めて練りなおして、Rxとは別枠となるかもですが提供する意思はあるそうで。「Don’t worry. Ix is not disappearing forever.」とのこと。そこで、フィードバックが欲しいとのことなので、答えてあげるといいんじゃないかしら。Asking your input - Interactive Extensions functionality。私も好きなメソッドと嫌いなもの(XxxEnumerableがIntelliSenseを汚染して嫌いだった)、それとドサクサに紛れて再帰/ツリー探索系のメソッドを入れてよ!とリクエストしておきました。
NuGet
6月頭にバージョンが少し上がって、現在はStableがv1.0.10605、Experimentalがv1.1.10605が最新バージョンとなっています。そしてNuGetのパッケージ名も少し変化して、Stable版はRx-MainなどプリフィックスがRx-のもの、ExperimentalはRx_Experimental-MainなどプリフィックスがRx_Experimental-と完全に別れて提供されるようになりました。
と、まあ
そんなところです。最近は私もこうして、更新されたよー、と言うだけで、技術的な情報提供を怠っていてすみませんすみませんなので、徐々に再開していきたいと思います。Schedulerの話とか非常に重要なのに、このブログでは一度も書いていないですしね、全くもってイクない。こないだのセッションReactive Extensionsで非同期処理を簡単には非同期中心だったので、イベントサイドについての話もしっかりまとめたいなあ、とかも思いつつ。
Reactive ExtensionsとSQLの非同期実行
DbExecutorのパフォーマンスが十分トップクラスであることは、こないだの計測で分かりました。では次のステップはどこへ向かおう。IL生成を頑張っても、もうほんの少ししか稼げる余地は残ってない。ならば、もっと根本的なところから行こう。ええ、非同期IOを。DbExecutorは一応、拡張性を考慮してあるので継承して非同期対応しましょう。
……ところで、この記事はAsyncDbExecutorの作り方、みたいになっていますが、読み取ってほしいのは「Reactive Extensionsの使い方」です。AsyncDbExecutorは、あくまでRxの利用法のサンプルにすぎません。DbExecutorなんて使わないしー、とか思わず、その辺を念頭において眺めてみてください。
BeginExecuteReader/EndExecuteReader
SqlServerならばSqlCommand.BeginExecuteReaderメソッドが使えます。IDbCommandにはないので、対象はSqlServer特化ということになってしまいますが、まあ、それはしょうがない(非同期対応してないDBもあるわけだし)。対応させる方法ですが、まずはDbExecutorを継承してコンストラクタを弄ります。
public class AsyncDbExecutor : DbExecutor { public AsyncDbExecutor(string connectionString) : base(new SqlConnection(connectionString)) { } public AsyncDbExecutor(SqlConnection connection) : base(connection) { } public AsyncDbExecutor(string connectionString, IsolationLevel isolationLevel) : base(new SqlConnection(connectionString), isolationLevel) { } public AsyncDbExecutor(SqlConnection connection, IsolationLevel isolationLevel) : base(connection, isolationLevel) { } }
通常のDbExecutorはIDbConnectionを受け入れるようにしていましたが、今回はSqlServer特化なのでSqlConnectionで。また、利便性を考えて生の接続文字列からも作れるようにしてやりました。IsolationLevelを受け入れるオーバーロードはTransaction処理する場合のためとなっています(別にTransactionScope使ってもいいですけどね)。あとは、非同期に対応するメソッドを作ってやれば良いだけ。
Reactive Extensions
BeginXxx-EndXxxがカッタルイのは分かりきっているので、当然のようにRxを持ち出します。
// 引数が横に長いですがほとんど省略可能なので…… public IObservable<SqlDataReader> ExecuteReaderAsyncRaw(string query, object parameter = null, CommandType commandType = CommandType.Text, CommandBehavior commandBehavior = CommandBehavior.Default) { var cmd = (SqlCommand)this.PrepareExecute(query, commandType, parameter); return Observable.FromAsyncPattern<SqlDataReader>( (ac, o) => cmd.BeginExecuteReader(ac, o, commandBehavior), cmd.EndExecuteReader) .Invoke() .Finally(() => cmd.Dispose()); }
this.PrepareExecuteですが、これはDbExecutorのprotectedメソッドで、クエリ文字列や匿名型で渡すパラメータからIDbCommandを生成します。こんなこともあろうかとちゃんとprotectedにしておいて良かった。そしてキャストですが、今回はconnectionは必ずSqlConnectionで(そうなるようコンストラクタを調整してある)、戻り値がSqlCommandであることが保証されているためアップキャストしてやります。そうしないと非同期APIが使えないですから。
あとは、いつものように(?)FromAsyncPatternして、それとFinallyでcommandをDisposeしてやることを忘れないと気が効いてるかもり。利用する時は
// async=trueを忘れずに var connstr = @"Data Source=.;async=true;Initial Catalog=master;Integrated Security=True;"; var executor = new AsyncDbExecutor(connstr); executor.ExecuteReaderAsyncRaw("select * from sys.tables") .Finally(() => executor.Dispose()) // 接続のDisposeも忘れずに... .Subscribe(dr => { while (dr.Read()) { Console.WriteLine(dr.GetValue(0)); } });
といった感じです。非同期APIはDisposeをどう仕込めばいいのかが悩ましいのですが、Rxなら簡単です、Finallyに突っ込めばいいだけ。同期APIでusingで囲むのと同じ感覚で、RxではFinallyに置いてください。非同期実行ということで、複数同時に走らせることも少なくないと思うのですが、その場合は全ての完了を自然に扱えるよう、結合して一本の流れにしてやる必要はあるでしょう。恐らくきっと。
そういえば忘れてはいけないのは、非同期APIを使う場合は接続文字列にasync=trueが必要です。これについてはADO.NET 2.0 における非同期コマンド実行によると
非同期コマンドを使用するためには、コマンドが実行される接続は、接続文字列を async=true と指定して初期化する必要があります。 非同期メソッドが接続文字列に async=true と指定されていない接続を使用するコマンドで呼び出されると、例外がスローされます。
所定の接続オブジェクトで同期コマンドのみを使用するとわかっている場合は、接続文字列に async キーワードを指定しないか、false に設定することをお勧めします。 非同期操作が有効になっている接続で同期操作を実行すると、リソースの利用率は著しく増大します。
同期 API と非同期 API の両方が必要な場合は、可能であれば別々の接続を使用することをお勧めします。 これが不可能であれば、async=true を指定して開かれた接続で同期メソッドを使用することもできます。この場合、通常どおりに動作しますが、パフォーマンスは若干劣化します。
とのことなので、少し気をつけたほうがいいかもしれません。まあ、Rx愛好家なら全てRxで非同期でやるに決まっているので(?)、別にasync=trueでも怖くありません。
SelectMany
しかし、生のDataReaderをwhileで回すとか、ダサ……。せっかくのRxなのだから値もPushで送ればいいぢゃない。
IEnumerable<IDataRecord> EnumerateSqlDataReader(SqlDataReader reader) { using (reader) { // Closeされるタイミングがコントロール出来ないので、IsClosedのチェックは必須 while (!reader.IsClosed && reader.Read()) { yield return reader; } } } // 前に定義したAsyncRawを呼んでSelectManyするだけ public IObservable<IDataRecord> ExecuteReaderAsync(string query, object parameter = null, CommandType commandType = CommandType.Text, CommandBehavior commandBehavior = CommandBehavior.Default) { return ExecuteReaderAsyncRaw(query, parameter, commandType, commandBehavior) .SelectMany(dr => EnumerateSqlDataReader(dr)); } // 使うときはこんな感じ var executor = new AsyncDbExecutor(connstr); executor.ExecuteReaderAsync("select * from sys.tables") .Select(dr => new { Name = dr.GetString(0), ObjectId = dr.GetInt32(1) }) .Finally(() => executor.Dispose()) .Subscribe(Console.WriteLine);
実にLINQっぽく自然になりました。yield returnを使ったのは、IObservableのSelectManyはIEnumerableも受け入れて平らにしてくれるからです。これ、Observable.UsingとかObservable.Generateを使って、Rxだけで頑張ることも可能ではあるのですが、面倒くさいしゴチャゴチャします。なので、yield returnが使えるなら、使ってしまったほうが楽。この辺はIEnumerable<T>を生成するためのyield returnがあるように、IObservable<T>を生成するためのコンパイラサポートが欲しいところ。awaitが乗ればTaskからIObservableへの変換(は基本的に容易)なので、ある程度可能になるのかなあ、と思いつつ、謂わばAsyncEnumerableになることの難しさもあるので今のところ何とも言えません。
ExecuteReaderAsyncがあればExecuteReaderAsyncRawいらないぢゃん!って感じですけれど、パフォーマンスのために非同期にするのに、SelectManyとかオーバーヘッドがあるのも嫌かなあ、と思う場合もあるかもなので、生のSqlDataReaderを返すものも残してあげるのもいいかな、とか思ったりはするところ。EnumerateSqlDataReaderのような拡張メソッドを別途定義してやれば、生のSqlDataReaderも、そう扱いが面倒というわけでもないですしね。いや、どうだろう……。
次バージョン
この調子でExecuteNonQueryやSelectなども書いていけば完成です。って、そういえばアクセサの動的生成&キャッシュの部分はinternalで外から触れない(せいぜいPrepareExecuteだけ)からSelectは書けないぢゃん。うげげ。うーん、publicにするのもどうかと思うので、InternalVisibleToで対処しようかなあ。そんなわけで、このAsyncDbExecutorは今はまだアイディア段階で内容を詰めてませんが、もっとブラッシュアップさせて、次のDbExecutorの更新時に含めたいと思っています。Rxが必要という都合もあるので、本体とは別DLLで。勿論、NuGet対応で依存解決でインストール楽々、です。
ところで本当に速いの?
うん、分かりません。ADO.NET 2.0 における非同期コマンド実行によれば
ADO.NET/SqlClient の非同期コマンド実行サポートは、実際に、本当の意味での非同期ネットワーク I/O (共有メモリの場合は非ブロッキングのシグナル通知) を基礎としています。 ご要望が多ければ、いずれ内部実装について文書にしたいと思います。 ここでは、”真の非同期” を行っており、特定の I/O 操作が終わるまで待機しているブロックされたバックグラウンドのスレッドは存在しない、と申し上げておきます。Windows 2000/XP/2003 オペレーティング システムのオーバーラップ I/O 機能と I/O 完了ポートの機能を利用し、単一スレッド (または少数スレッド) によって、所定のプロセスに対する未処理の要求をすべて処理することを可能にしています。
というわけで、まあ速いんじゃないかねえ、とは思うんですが、計らないことには分かりません。ベンチマークはAsyncDbExecutorのリリース時に、ちゃんと計ってみたいと思います。とりあえず非同期IOで万歳なNode.jsに負けてられませんからね(謎)。というのはともかく、この辺は以前にmono meetingでAzure Tableのパフォーマンスの話を聞いた時→資料:20110126 azure table in mono meeting全くサッパリだったこともあるので、ちゃんと調べたいとずっと思っていたのです(が、IOCPとかネイティブな話はさっぱり&データベースの挙動の中身も全然なので、あくまで.NETな上層のほうで…… いずれは何とかしたいのですけれど、手一杯でどうにも)。
やっぱ非同期が必須なのはサーバーサイドの話になるのですかねえ。IHttpAsyncHandlerとRx、とかそのうち書きたいのですが、そもそも私はASP.NETあんま分かりませんですよというところから始める必要があり。調べたいことがありすぎて積みタスクの山にうもれて完全死亡中。
デフォルトExecutor
最後に話は変わりますが、new DbExecutorするのに毎回コンストラクタにSqlConnection渡すのがダルいし不自然!という場合は、これまた継承して接続文字列が固定されたExecutorを用意すると良いです。
public class HogeExecutor : DbExecutor { public HogeExecutor() : base(new SqlConnection("Data Source=hogehoge;")) { } public HogeExecutor(IsolationLevel isolationLevel) : base(new SqlConnection("Data Source=hogehoge;"), isolationLevel) { } }
中々悪くないのではないでしょーか。残念ながらStaticメソッドのほうは使えませんが。Staticメソッドのほうは、どうも上手いやり方が考えつかなくて色々と保留中。考えてはいるのですが。
Reactive Extensionsで非同期処理を簡単に
すまべん特別編「Windows Phone 7 開発ブーストアップ」@関東にて、Reactive Extensionsの概要と、特に非同期を中心に話しました。以下、発表資料になります。
当初は初心者向け、と思ったんですが、どう見ても一回触ったことのある人向けですねこれ……。Rxを触ったことない人は、よくわからないけど普通に書くと大変なのがスッキリして何だか凄そう、触ってみようと思ってもらえれば。Rxを既に触っている人には使いこなしのTipsとして役立てて貰えればと思います。
はい、Ustreamも残っています。マイクがなかったからという言い訳をしますが、声が全然入ってないですね、声入ってないということは会場でもモゴモゴーという感じで聞き取りにくかったはずで、すみません。それと、「まぁ」言い過ぎ。繋ぐ言葉を捜す枕詞として使いまくりで、うわちゃー、という感じ。慣れてないというか、こういうの(ほぼ)初めてで全然分かってなかったんですが、これを活かして次も頑張りたいです。(Usreamで自分の発表が自分で後から見れるのは自分が嬉しい(笑))。スピーカーできて楽しかったです。機会をくれたすまべんの方々に感謝します。
……こういう機会がないと中々まとめないよね、というのもかなりあったり。合成系のメソッドの図はずーっと書こうと思っていて、書いてなくてこのセッションが初めてです。IObservableは時間軸に乗っているという話もこれが初めて。Twitterでボソッと書くだけじゃなくて、しっかりブログにまとめるようにならないといけないなぁ。
Reactive Extensionsによる非同期クエリ
InfoQ: Future、性能、依存性の低減など多くの改善がされたAkka 1.1リリースから「- Futureは完全にモナドになった。したがってfor内包表記を利用できる。」。リスト内包表記は、ようするところLINQなわけなので、では、とりあえずC# + Reactive Extensionsで。
// 非同期に対するLINQ(map, filter, etc...) var asyncQuery = from a in Observable.Start(() => 10 / 2) from b in Observable.Start(() => a + 1) from c in Observable.Start(() => a - 1) select b * c; // 非同期のまま実行したいならSubscribe var canceler = asyncQuery.Subscribe(Console.WriteLine); // 実行をキャンセルする場合はSubscribe時の戻り値をDispose canceler.Dispose(); // 同期的に待って値取得したいならFirst var result = asyncQuery.First();
Rx抜きで、TaskのContinueWithで↑を書くのはカッタルイ。また、Rxでもメソッド構文でSelectManyを連鎖でも辛い。何故か、というと、aの値をcの部分で使えないから。メソッドチェーンの形だと、どうしても一つ手前の値しか持ち越せない。そこで、クエリ構文が活きます。また、LINQであるが故にクエリ構文が使えるRxの嬉しさ。
じゃあ、調子にのってFutures (Scala) — Akka DocumentationをRxで書き換えてみようかしら。同じような内容としては以前にRxを使って非同期プログラミングを簡単にという記事でTaskと比較していたのでそちらも参照を。
// directly var f1 = Observable.Return("Hello World"); // LINQが使える var f2 = f1.Select(x => x.Length); // Subscribeまで実行が遅延されるのでSleepしないよ var f3 = Observable.Defer(() => { Thread.Sleep(1000); return Observable.Return("Hello World"); }); var f4 = f3.Select(x => x.Length); // まだ実行されないよ var result = f4.First(); // ここで実行 // Observableの連鎖はSelectManyで var f5 = f1.SelectMany(x => f3); // SelectManyはクエリ構文のfrom連打でも置き換えられる var f6 = from a in Observable.Return(10 / 2) from b in Observable.Return(a + 1) from c in Observable.Return(a - 1) select b * c;
と、この辺まではいいんですが、Composing Futuresが何やってるのかよくわからないので(Scala知識ゼロですみません)、眺めながらAsync CTPでも持ち出します。
async void HomuHomu() { var f1 = Observable.Return(100); // IObservable<int> var f2 = TaskEx.FromResult(200); // Task<int> var a = await f1; // 何気にIObservable<T>はawait出来る var b = await f2; // 当然ですがTask<T>もawait出来る var result = a + b; // 300 // じゃあObservableが幾つも値持ってる場合は? var f3 = new[] { "homu", "mado" }.ToObservable(); var c = await f3; Console.WriteLine(c); // "mado" // つまり、完了まで待って(OnCompleted)、最後の値が取得される // ところでObservable.Return = TaskEx.FromResultなわけですが // 以下の3つも同じと捉えていいです Task.Factory.StartNew(() => 100); TaskEx.Run(() => 200); Observable.Start(() => 300); // つまりfunc自体は即時実行 // こちらも等しい(実行が遅延される) var t = new Task<int>(() => 100); var o = Observable.ToAsync(() => 100); // 実行するには t.Start(); o.Invoke(); // もしくは o() ←ただのデリゲートなので // ToAsyncでは実行時に引数を渡すことも可能 var o2 = Observable.ToAsync((int arg) => arg * 2); o2(1000).Select(x => x).Subscribe(Console.WriteLine); }
雰囲気で何となくそうなのだろうと思いつつ、よくわからないので、適当に解釈しながら次。
// IObservable<T>はそのものがリスト状態とも言えるので、 // 複数値を持てるし、LINQなのでSelectしてAggregateも出来る var futureSum = Observable.Range(1, 1000) .Select(x => x * 2) .Sum(); var sum = futureSum.First();
とりあえずこの辺で(特に言いたいことはない)。全く読めないとこういう時辛い。ActorとReactiveの関係とは、とか、見えそうな見えないような気持ち悪さが脳に渦巻いていて、勉強したいところです。F#で。
上の話とは関係なく告知
二件ほどお話を頂いたので、セッションします。まず、2011/05/21(Sat)にすまべん特別編でRxについて。内容はRx全般になるので、WP7ではなくても適用出来る話になります。
Rxの多くの機能のうち非同期に絞って、かつ、初心者向けに説明しますので、Rxって何それ食べれるのという感じでも全然大丈夫です。また、既に触っている人も、割とためになるTipsが得られるのではないかなと思いながら資料作成中。そうなるよう頑張ります。なので、是非聞きに来てください。セッション資料は、通信環境があればセッション終了後即座に上げるつもりです。なければまた後日で。Ustreamとかもあるのかな?あれば、そちらでも。
もう一件、5月23日(月)にC#ユーザー会でCode Contractsについて。
背景であるDbCなどについてはufcppさんが説明してくださるので、私はCode Contractsとして実装されていることを、Reflectorでこうリライトされるんですねー、とか見ながらデモ中心に、「一から使ってみよう」といった内容にしようと思っています。Code Contracts…… 名前だけなら聞いたことがある、いや、名前も聞いたことない何それ、ぐらいからが対象なのぜ、是非どうぞ。もう使っている、という人には物足りないかも(むしろそこは私が教えて欲しいもがもがもがもが)。
どちらも、まだ参加申し込み出来るようなので是非聞きにきてください。
Reactive Extensions RC0リリースによる変更点
DevLabsの実験的プロジェクトからData Developer Centerへの正式プロジェクトとして昇格したRxですが、ついにDevLabsのページ自体が消滅(リダイレクトされる)し、いよいよ実験的なノリは一段落し、正式なプロジェクトとしての道を歩み始めたようです。その手始めとして、馬鹿デカい破壊的変更がやってきました。……っていきなりなんじゃそりゃ。
今回の変更はRC0、そしてStableであると銘打たれ、(今度こそ)APIの破壊的変更はないものと思われます。きっとAPIを変更するなら本当の本当に最後のタイミングだから、ということなのでしょうね、変更の嵐は。そんなわけで、DLLの分け方が変わってるし、名前空間も全部変わってるし、メソッド名もばかばか変わってるし、メソッドシグネチャも変わってるし、なくなったのもあるし、大量すぎて書ききれないほどに変更点がある。
しかし、本当の本当に安定版の始まりなので、つまり、学ぶなら今からが正に最適ということです!また、基本的な使い方が変わったわけではないので、既存の知識は生かせますしコード自体もそのままでも大体は行けます。大体は。
詳しい変更内容自体はフォーラム New Release: Reactive Extensions v1.0.10425 に書かれています。こういうのはフォーラムじゃなくてBlogのほうでちゃんと告知してください……。リリースから半月近く経つのに、まだBlogのほうは更新されてないという。
Rxの入手方法・パッケージ・DLL内容について Part2
以前にReactive Extensionsを学習するためのリソースまとめとしてまとめましたが、そのうちDLL内容がまるっと変わったので、そこの部分を修正します。なお、学習リソースについては変りないので、以前のまとめ記事をそのまま参照してください。また、Windows Phone 7に標準搭載されているものは(当然)変わりはありません。最近WP7本体のアップデートにコソッと紛れて更新(バグフィックス)されたようですけど。
Reactive ExtensionsのGet itからDownloadするといいでしょう。色々書いてありますが、2のDownload the Reactive ExtensionsからRx for all Platformsを選べばよいかと思います。
StableとExperimentalがあるように、安定版と実験版に分かれています。そして現時点ではそのページのExperimentalをクリックしてもExperimentalは手に入りません(なにそれ……)。Experimentalが欲しい人はMicrosoft Download Center: Search ResultsのExperimental Releaseから入手するといいでしょう。バージョン番号は、Stableは1.0.10425、Experimentalは1.1.10425になっています。
けれど、何だかんだで更新頻度が高いので、NuGet経由での利用が一番お薦めです。
が、NuGetの画面は新旧入り乱れていて何を入れていいか分からないカオスになっていたりして。説明すると、Rx-Mainとか、名前がRx-になっているものが新しいもので選ぶべきもの。Reactive Extensions -のものは古い残骸なので無視してください。また、NuGet経由で入るものはExperimentalのほうになります。
さて、今回からDLL類の分類がばっさり変わりました。
- System.Reactive (NuGetではRx-Main)
以前あったCoreExは消滅し、コアコンポーネントはSystem.Reactiveのみとなりました。また、名前空間も全てSystem.Reactive以下に集められていて、例えば今までObservableクラスはSystem.Linqでしたが、今回よりSystem.Reactive.Linqに変更されました(拡張メソッドの利用に名前空間の参照は必須なので、この名前空間のusingは忘れずに)。
- System.Reactive.Windows.Threading (NuGetではRx-WPF/Rx-Silverlight)
WPF/Silverlight向けでDispatcherに対するObserveOnとSubscribeOnのオーバーロードが追加されています。また、Dispatcher.CurrentDispatcherに対してObserveOn/SubscribeOnを行うObserveOnDispatcher/SubscribeOnDispatcherの利用もこのDLLの参照が必要になりました。それとDispatcherに対してBeginInvokeして実行するスケジューラDispatcherSchedulerが追加されました。
WPF/SilverlightでRxを使う場合はこれの参照は必需と思われます。NuGetを使う場合は依存の解決で、これを選択するとRx-Mainも入れてくれるので、こちらからInstallすると良いでしょう。
- System.Reactive.Windows.Forms (NuGetではRx-WinForms)
WindowsFormsのControlに対するObserveOnとSubscribeOnのオーバーロードが追加されています。それとControlScheduler(Controlに対してBeginInvokeして実行するスケジューラ)。それだけです。というわけで、その名の通りWinFormsで使う場合だけ、あると便利。
- System.Reactive.Providers (NuGetではRx-Providers)
Qbservableが収納されています。QbservableはIEnumerableに対するIQueryableみたいなもので、式木からObservableを生成するためのもの。今のとこ有効活用されている例も人もいないと思われます。(というかよく正式リリースにも生き残ったものだ、ぐらいの)。ちなみにQueryable Observableの略だそうで。QBみたいなものだと思えば可愛い!
例えばLinq to Twitterを非同期しかないSilverlightでやるなら、これを使うのが適切でしょう。現状だとコールバックでどうだのという格好悪い仕組みなので。(但し、System.Reactive.Providersは現状ではDesktop版にしか同梱されていませんが)。あとは公式の例として挙げられているWQL Provider。WMIに対するSQLで、クエリ結果はイベント(つまりRx)になる。非常に都合よくQbservableに当てはまるようになってますね。なってるんですが、そう都合よく当てはまるのはこれぐらいしかないのではないか、感もあったり。
いや、むしろ全て非同期なら全部Rxでいいんですよぅー、to SqlだってSqlCommandのBeginExecuteReaderでやればIQueryableじゃなくてIQbservableの出番なんですよぅー。……とはいっても、誰がやるかって話ですね。
- Microsoft.Reactive.Testing.dll (NuGetではRx-Testing)
ユニットテスト用のモック生成クラス群。使いやすさ的には個人的にはちょっと微妙で、今一つ上手く活用出来なくて色々見送り中。
FromEvent/FromEventPattern
BufferWithCount/BufferWithTimeが統合されてメソッド名がBufferになった、程度の変更は割とどうでもいいのですが、Rxの中核であるFromEventに大きな変更があったのは見逃せません。簡単に解説します。
今までFromEventというメソッド名だったものはFromEventPatternに変わりました。また、戻り値がIEventからEventPatternというものになりましたが、中身はプロパティにSenderとEventArgsを持つという、ほとんど同じものなので、感覚的には一緒です。今までの私の記事や古いウェブ上の記事を見る際にFromEventが使われていたら、それはFromEventPatternに置き換えてください。そうすれば、そのままで動きます。
では新しく新設されたことになるFromEventは何なのかというと、よくわかりません:) そのうち使い方の説明とか出てくると思うのでそれ待ちで……。いやすみません。
ところでInteractiveはどうしたの?ClientProfileは?Asyncは?
死にました。というのもアレですが、とりあえず先行きは不透明です。少なくともStableに同梱されることはないそうです。Experimental側でのリリースは、一応計画はされているようですが、現状は同梱されていません。どうなるんでしょうかねえ……。Interactiveは欲しい人はNuGetに古いのが残っているので、それを使えば、ですね……。
Experimental
さて、今のところ、どのメソッドが実験的とされているのか、見てみましょう。幸いExperimentalAttributeでマークされているので、確認はコードで容易に出来ます。
typeof(System.Reactive.Linq.Observable) .GetMethods() .Where(mi => mi.GetCustomAttributes(typeof(ExperimentalAttribute), false).Any()) .Select(mi => mi.Name) .Distinct() .OrderBy(s => s) .ToList() // Interactiveが消滅してしまったから... .ForEach(Console.WriteLine); // 実行結果 Case Create // 追加のオーバーロードのみ、通常のはExperimentalではない DoWhile Expand For ForEachAsync ForkJoin GetAwaiter If IsEmpty Let ManySelect Remotable Start // 追加のオーバーロードのみ、通常のはExperimentalではない While
あまり大したものは入ってないようなので、そんな気にすることもないですね。Expandは是非入れてきて欲しいところなのだけど。CreateやStartのオーバーロードはかなりややこしい事になっているので、実験的扱いなのは納得。
ReactiveOAuth ver.0.3.0.0
そんな大移動があったわけでReactiveOAuthも動かなくなってしまった。というわけで更新しました(WP7版の人は気にしなくてもいいです)。
機能は変わってなし。とりあえず最新版のRxで動くように、というだけです。コードが、とにかく名前空間が変わったので全部書き換えて、かなり面倒……。そして一部のメソッドは名前が変わったので、WP7版と完全にコードを共有していたので発狂。ifディレクティブでメソッド呼び出し部分をひたすら書き換え、などというのは見通しも悪いし格好悪いしで最悪なので、別の方法として、WP7側に拡張メソッド作ってコード上の互換を維持するようにしました。
public static class ObservableForCompatible { // 本体のコードはRx RC0に合わせて、WP7側だけ拡張メソッドで同名のものを作って対処 public static IObservable<IList<T>> Buffer<T>(this IObservable<T> source, int count) { return source.BufferWithCount(count); } }
他にも挙動が若干変わってるのがあって原因掴むのに泣きそうになったりとか、思った以上に大変だった……。Stableと銘打ってるのに、次にこのクラスの大変更があったらさすがにブチ切れます。今回は、まあ、許す。
Reactive Extensions Extensions(Rxx)
コミュニティから面白いライブラリも上がってきています。
Rx拡張メソッド集。C# 3.0の時も俺々拡張メソッドライブラリがいっぱい出てきましたが、そのノリですね。でも実際、Rx自体は原始的な機能のみなので、非同期処理とかイベント処理にフォーカスする場合は、もう一つ上の層で軽くラップしたライブラリは間違いなく必要だなと思っていますので、こういうのはいいな、と。私自身も非同期処理はReactive Extensions用のWebRequest拡張メソッドとして、結構ガッツし仕込んだものを使い回していて(ReactiveOAuthやUtakotoha の内部はこれ)かなり重宝しています。余裕が出たら、このRxxプロジェクトにJoinしたいな、と思ってます。
Rxの本
オライリーから出ているProgramming C#でお馴染みのJesse Libertyと、共著者としてReactiveUIというRxでWPFのGUI面をサポートするライブラリを作成しているPaul Betts(Microsoft Office Labsに所属)による、Rxの本が今年の秋頃に出る予定です。
執筆陣が豪華だしページ数も現時点amazon表示で300ページと、立派な仕上がりを予感させます。私は予約したよ!
まとめ
今回こそStableなはずなので、ようやーく人に自信を持って薦められるようになりました!いやマヂで。API的にも多分、じゃなくて絶対安定したわけなので、飛び込むなら今!です。
ところで本題とは関係ないんですがスマートフォン勉強会 - すまべん特別編「Windows Phone 7 開発ブーストアップ」@関東でWP7+Rxのセッションをします。入門編として、主に非同期処理にフォーカスして、今すぐコピペで使ってコールバックを撲殺しよう、といった内容を考えていますので、是非聞きに来てください。
Linq と Windows Phone 7(Mangoアップデート)
- C# Rx WindowsPhone7 - 11.04/14
今冬に予定されている大型アップデート(Mango)に向けて、MIX2011で大量に情報出てきました。色々ありましたが、その中でも目を引いたのがデータベース対応とQueryable対応。データベースは恐らくSQL Server Compact 4.0相当で、これはいいものだ。ただ、生SQL文かあ、という思いもあり。他のやり方ないのかなあ。オブジェクトDBとか?などと夢見てしまうけれど、高機能なエンジン(SQLCE4)がある以上は、それをそのまま使ったほうがいいのは自明で。
インピーダンスミスマッチを解消する、そのためのLinqでしょ!と、ああ、そうだね。でもLinq to Sqlはないし、Linq to Entitiesが乗っかるわけもないし、策はなく普通に生SQLの気もするなあ。どうでしょうね。どうなるのでしょうね。で、Queryable対応も発表された、というかデモられていました。
これでDB+Linqがあることが保証されたから安泰。何のQueryProviderと見るべきかしら。この辺、不透明なので、情報入り次第しっかり追っかけます。
Queryableの難点は、見た目はスッキリしてますが、コンパイル後の結果は式木のお化けになる+実行時は式木のVisitというわけで、軽い処理とは言い難いところなのですが、軽いとか重いとか気にしないし!じゃあなくて、MangoではGC改善など性能向上も入るようなので、十分なパフォーマンスは確保できそうです。あと、ユーザーエクスペリエンスを損なうボトルネックは結局そこじゃあないんだよ、的なこともありそうだし。
例えば現行のWP7のListBoxは致命的に重くて、純正アプリは軽いのにユーザー作成アプリではクソ重く、純正だけネイティブでチートかよゴルァ、という勢いだったんですが(Mangoで改善されるそうです!)、これの要因の一つは画像の読み込み周りがアレだったことにあるようで……。
CocktailFlowという素敵な見た目のアプリがあるのですが、これはその辺の問題に対処するためか、事前にLoadingを取って、完全に読み込み終わってからしか表示しないようになってます。でも、それはそれでネットワーク読みに行ってるわけでもないのにLoadingが長くて、微妙だなあ、と。
MIX11でのRx
二つセッションがありました。一つはLinq in Actionの著者(最初期に出たものですが、良い内容でしたね)によるWP7でのRx活用例。
マイクのボリュームが不安定で聴きにくいですー。話はPull(Enumerable)とPush(Observable)の比較から入ってますね。MoveNextして、MoveNextして、MoveNextして、ええ、ああ、メソッド探訪第7回:IEnumerable vs IObservableの辺りで書きましたね~。それのEnumerbaleサイドのリライト版であるLINQの仕組みと遅延評価の基礎知識の続きとして「Rxの仕組み」を書きたいのですが書くと思ってもう3ヶ月も経ってる、うぐぐ……。
話がそれた。んで、話の序盤は結構退屈です。IEnumerator vs IObserverとかつまらないです、って、あ、私もやってたっけ……。話の流れを上手くつながないと何か唐突なのね。さて、デモはサイコロ転がしを完成させていくというもの。どんどんコードがメソッドチェーンのお化けになっていく様はどこかで見たことある(笑)。最初は、重たい処理を、ObserveOn(Scheduler.ThreadPool)とするだけで実行スレッドを切り替えて、CPUバウンドの処理を簡単に同期→非同期に変換してUIをブロックしないというデモ。ちなみにObserveOn(Scheduler.ThreadPool)じゃなくて、これはToObservableの時点でScheduler渡したほうがいいんじゃあないのかなあ。まあ、大して違いはないといえばないですがー。
次は加速センサーをRxに変換して、TimestampをつけてShakeされたことを検出するというもの。生のセンサーイベントをRxで事前に加工して扱いやすくする、という手ですね。私もUtakotohaの再生イベントでそれやったよ!(いちいち対抗心燃やして宣伝しなくてよろし)。ObservableだとMockに置き換えやすいんだよ、という話もね。気づいたらメソッドチェーンのお化けになっているのも。Linq使いの末路はみんなこんなのになってしまうという証明が!
セッションはもう一つ。RxチームのBart De SmetによるRxJSのセッション。MIX2011はIE10と合わせてHTML5推しもありましたしね。
最初の20分は、ObservableとObserverの購読概念などについて簡単な説明。あとRange,FromArray,Timerといった生成子。Pullとの比較がない、つまり”Linq”とは全く言わないのがJavaScript向けですね。喩えもHTML5のGeolocation APIが、などなど新鮮。そして、だからこそ分かりやすいかもしれません。その後はいきなりGeolocation APIをラップする自作Observableを作ろうが始まって難易度が、まあLevel300だものね。実用的な話ではあるし、Observableを作るということは仕組みを理解するという話でもあるので、こういうのも良い流れ。ほか、KeyUpを拾ってネットワークを問い合せてのリアルタイム検索(event+asynchronousの合成例)など。これはハンズオン資料でもお馴染みの例ですが、順に追ってデモしてもらえるとThrottleの意味は凄く分かりやすいですね。Switchも強力だけど分かりづらさを持つので、こうして丁寧に解説してもらえると、いいね。ともかく、概念としてC#もJavaScriptも超えたところにあるので、考え方、コード例はC#/WP7でも使えます。良いセッションだと思いますので見るのお薦め。
そういえばで、Async CTPが更新されていて、一部変更があったので、RxとAsyncとのブリッジライブラリ(Rx-AsyncLinq)が動かなくなったと思われるので、それに合わせて近いうちにRxもアップデートありそうな気がします。RxJSもここずっと更新とは遠かったのですが、こうしてセッションもあったことだし、大型アップデートはあってもおかしくないかな。
Async CTPはufcppさんの記事Async CTP Refreshに詳しく書かれていますが、ライセンス形態がご自由にお使いください(※但し自己責任)へと変更、日本語版でも入るようになった、WP7対応、といった具合に、WP7開発でも今すぐ投下しても大丈夫だ問題ないになったのは大きい。
他言語からWP7開発のために来たので、C#の経験があまりなくてLinqがイマイチ分からない。でも非同期ダルい。という人には、Rxよりも、Asyncのほうがずっと扱いやすく応えてくれるので、なし崩し的に投入してしまっていい気がしますねー。勿論、ある程度は大きな変更に対応していく気は持ってないとダメですが。
ある程度Linqが分かる人なら(別にそんな大仰なスキルを要求するわけじゃなく、SelectしてWhereして、Linq便利だな~、ぐらいでいいです!)Rxを学ぶのもお薦め。基本的にはLinqに沿っているので、学習曲線はそんなにキツくないです。特に、非同期をラップしてWhereしてSelectしてSubscribe、ぐらいならすぐ。他のは、徐々に覚えればいいだけで。
まとめ
WP7はRx標準搭載で、ただでさえLinq濃度が高かったというのに、Queryable搭載でLinq天国に!もはやWP7が好きなのかLinqが好きなのかよくわかりませんが(多分、後者です)、ともかく最高のプラットフォームなのは間違いない。
ところでDay1ではnugetが割とフィーチャーされていたのですが、その背景、よくみると……
右のほうにlinq.jsが!すんごく嬉しい。
Windows Phone 7でJSONを扱う方法について(+ Bing APIの使い方)
- C# Rx WindowsPhone7 - 11.03/31
C#と親和性の高いデータ形式はXMLです。何と言ってもLinq to Xmlが強力です。また、SOAPも悪くない、というのもVisual Studioの自動生成が効くので何も考えずともホイホイ使えます。ではJSONは、というと、これは割と扱いづらいところがあるのが正直なところ。しかしWindows Phone 7においては、JSONを選択すべきでしょう。なにせ、モバイル機器。ネットワークがとても貧弱。データは小さいに越したことはない。XMLとJSONとでは、雲泥の差です。
WPFではJsonReaderWriterFactory(と、内部にそれを用いたDynamicJson)、SilverlightではSystem.Jsonなどが用意されていますが、WP7には一切ありません。じゃあどうするかといえば、シリアライザを使います。WP7ではDataContractJsonSerializerが標準で用意されている(WPF, SLにもあります)ので、それを使ってデシリアライズしてJSONをオブジェクトに変換するのが基本戦略となります。
外部ライブラリ、Json.NETを使うという手も勿論ありますが。
BingからのJSONの取得
何はともあれ、サンプル題材のJSONを拾ってきましょう。Webからの取得というと、最近はいつもTwitterのPublic Timelineでマンネリ飽き飽きなので、別のものを。WP7なので、Bing APIを使いましょう!Bing APIはIDを取得しないと使えないのでサンプル的にどうよ、というところもありますが、IDの取得は簡単(ほんとワンクリックです)だしWP7と親和性の高いAPIでもあるので、これを機に、試しに取ってみるのも良いのではと思います。画像検索、翻訳など色々種類があるのですが、今回はWeb検索(sources=web)にします。
// 標準WP7テンプレのMainPage.xaml.csにベタ書き const string AppId = ""; // AppIdは登録してください Uri CreateQuery(params string[] words) { // countなどは変数で置き換えれるようにするといいのではと思います、ここでは固定決め打ちですが var query = "?Appid=" + AppId + "&query=" + Uri.EscapeUriString(string.Join(" ", words)) + "&sources=web" + "&version=2.0" + "&Market=ja-jp" + "&web.count=20" + "&web.offset=0"; return new Uri("http://api.search.live.net/json.aspx" + query); } public MainPage() { InitializeComponent(); var wc = new WebClient(); Observable.FromEvent<DownloadStringCompletedEventHandler, DownloadStringCompletedEventArgs>( h => h.Invoke, h => wc.DownloadStringCompleted += h, h => wc.DownloadStringCompleted -= h) .ObserveOnDispatcher() .Subscribe(e => { var json = e.EventArgs.Result; // ダウンロード結果(json文字列) MessageBox.Show(json); }); wc.DownloadStringAsync(CreateQuery("地震")); }
json.aspxにクエリ文字列をつけてGETするだけなので割とお手軽。クエリ文字列がゴチャゴチャして分かりづらいのですが、基本的に弄るのはqueryとweb.countぐらいかな、と思います。BingのReferenceは、生成元のクラス構造がまんま掲示されているだけで、恐ろしく分かりづらいので、適当にサンプルから当たりをつける感じで。
非同期通信の実行はReactive Extensions(Rx)で行います。Windows Phone 7では標準で入っているのでSystem.ObservableとMicrosoft.Phone.Reactiveを参照に加えてください。非同期通信を生でやるなんてありえませんから!Rx利用を推奨します。
得られるJSONは下記のものです。
{ "SearchResponse": { "Version": "2.0", "Query": { "SearchTerms": "地震" }, "Web": { "Total": 88, "Offset": 0, "Results": [ { "Title": "地震情報 - Yahoo!天気情報", "Description": "Yahoo!天気情報は、市区町村の天気予報、世界の天気...", "Url": "http://typhoon.yahoo.co.jp/weather/jp/earthquake/", "DisplayUrl": "typhoon.yahoo.co.jp/weather/jp/earthquake", "DateTime": "2011-03-29T19:11:00Z" }, { "Title": "地震情報 :: ウェザーニュース", "Description": "最新の地震の震度、震源地、震度分布を速報で届けます...", "Url": "http://weathernews.jp/quake/", "DisplayUrl": "weathernews.jp/quake", "DateTime": "2011-03-28T09:10:00Z" }, // 配列上なので幾つも... ] } } }
JSONに関してはJSON ViewerをVisual StudioのVisualizerに組み込むとかなり快適にプレビュー出来るようになります。が、カスタムVisualizerはWP7では実行出来ないのでテキストで見て、スタンドアロンのものにコピペってのを実行ですね、しょんぼり。
DataContractJsonSerializer
では、JSONをオブジェクトに変換しましょう。基本的には、1:1に対応するクラスを作るだけ。必要に応じて System.Runtime.Serializationの参照を加えDataContract, DataMember属性なども加えればよし。
public class BingWebRoot { public SearchResponse SearchResponse { get; set; } } public class SearchResponse { public string Version { get; set; } public Query Query { get; set; } public Web Web { get; set; } } public class Query { public string SearchTerms { get; set; } } public class Web { public int Total { get; set; } public int Offset { get; set; } public Results[] Results { get; set; } } public class Results { public string Title { get; set; } public string Description { get; set; } public string Url { get; set; } public string DisplayUrl { get; set; } public string DateTime { get; set; } }
JSONは、JavaScriptのオブジェクトとほぼ同一の記述ですが、ようするに{}になっている部分はクラスで、[]になっている部分は配列で、置き換えていけばいい、ということで。難しくはないのですが、面倒くさいには大変面倒くさい。なお、JSONの構造を全部記述する必要はなく、必要なものだけでも構いません、例えばVersionやQueryはいらないから省くとか、全然アリです。
そして、 System.ServiceModel.Web を参照設定に加え、DataContractJsonSerializerを使います。
var wc = new WebClient(); Observable.FromEvent<OpenReadCompletedEventHandler, OpenReadCompletedEventArgs>( h => h.Invoke, h => wc.OpenReadCompleted += h, h => wc.OpenReadCompleted -= h) .ObserveOnDispatcher() .Subscribe(e => { using (var stream = e.EventArgs.Result) { var serializer = new DataContractJsonSerializer(typeof(BingWebRoot)); var result = (BingWebRoot)serializer.ReadObject(stream); MessageBox.Show(result.SearchResponse.Web.Results[0].Title); } }); wc.OpenReadAsync(CreateQuery("地震"));
デシリアライズはReadObject、シリアライズはWriteObjectで行います。基本はstreamを渡すだけでオブジェクトの出来上がり。
with Reactive Extensions
ですが、まあ、Resultsが欲しいだけなのにSearchResponse.Web.Resultsは長げーよ、とか、DateTimeがstringでイヤだー、とか色々あります。そういう場合はJSONとのマッピング用のクラスとは別に、アプリケーション側で使うクラスを別に立ててやればいいんぢゃないかしら。
public class SearchResults { public string Title { get; set; } public string Url { get; set; } public DateTime DateTime { get; set; } public override string ToString() { return DateTime + " : " + Title + " : " + Url; } }
TitleとUrlとDateTimeしかいらない!という具合で。これを、今度はWebRequestを使って書くと
var req = WebRequest.Create(CreateQuery("ほむほむ")); Observable.FromAsyncPattern<WebResponse>(req.BeginGetResponse, req.EndGetResponse)() .Select(r => { using (var stream = r.GetResponseStream()) { var serializer = new DataContractJsonSerializer(typeof(BingWebRoot)); return (BingWebRoot)serializer.ReadObject(stream); } }) .SelectMany(x => x.SearchResponse.Web.Results) .Select(x => new SearchResults { DateTime = DateTime.Parse(x.DateTime), Title = x.Title, Url = x.Url }) .ObserveOnDispatcher() .Subscribe(x => { // 加工は全部終わってるのでここで色々自由に処理 Debug.WriteLine(x); });
となります。最初のSelectは非同期の結果、次のSelectManyではResults[]、つまり普通の配列を平坦化して、以降は普通のLinqのようなコレクション処理をしています。
非同期リクエストとオブジェクトのコレクション処理が、完全にシームレスに溶け込んでいます。これが、RxがLinqとして存ることの真価の一つです。記述が統一され、かつ限りなくシンプルになる。Rxは非同期が、イベントが、時間が、簡単に扱えます。でも、本当の真価は単独で使うというだけでなく、それらが全てPush型シーケンスに乗っていることで、統合することが可能だというところにあります。
でも、むしろ分かりにくい?ふむむ……。慣れの問題、などというと全く説得力がなくてアレですが、しかし、慣れです。記述がシンプルになり、柔軟性と再利用性が増していることには間違いないわけで、後は一度全て忘れてLINQの世界に飛び込んでしまえばいいと思うんだ。
Linqは各処理の単位が細分化されている(Selectは射影、Whereはフィルタ)ことも特徴ですが、これは思考の再利用可能性を促します。非同期->オブジェクト配列=SelectManyなど、単純な定型パターンに落とし込めます。C#はもとより強力なIntelliSenseにより、ブロックを組み立てるかの如きなプログラミングを可能にしていますが、Linqでは、それが更に先鋭化されていると見れます。
まとめ
これも現在製作中のWP7アプリからの一部です。最近Bing API利用に切り替えたので。無駄に汎用化して作りこみつつきりがないので適度なところできりあげつつ。ユニットテスト作ってあったので移行自体は幸いすんなりいった。良かった良かった。テスト大事。
Bing APIの前は諸事情あってGoogleからのスクレイピングでした。スクレイピングはグレーだろうということで代替案をずっと探していて、何とかBingに落ち着きました。最初はどうにも使い物にならない、と思ったのですが、検索パラメータを色々変えて、ある程度望む結果が出るようにはなったかな、と。Bingは結構癖があって、調整大変ですね。その話は後日、WP7アプリが完成したときにでも……。
コード的にはスクレイピングのほうも割と凝ってたんですけどねー、バッサリとゴミ箱行き。復活することは、ないかな。もったいないけどしょうがない。いつかそのうち紹介する日は、来るかも来ないかも。
そんなわけで延々と足踏みしていて実装は相変わらず一歩も進んでませんが(!) 順調に制作は進行中なので乞うご期待。いやほんと。
Rx + MolesによるC#での次世代非同期モックテスト考察
最近、妙にテストブームです。Chaining Assertionを作ったからですね。ライブラリドリブンデベロップメント。とりあえずでも何か作って公開すると、その分野への情報収集熱に火がつくよね。そしてテスト厨へ。さて、ユニットテストで次に考えるべきは、モックの活用。C#でモックといえばMoqが評価高い。メソッドチェーンとExpression Treeを活かしたモック生成は、なるほど、良さそうです。読み方も可愛いしね。もっきゅ。もっきゅ。
というわけでスルーして(えー)Molesを使いましょう。Microsoft Research謹製のモックフレームワークです。PexとのセットはMSDN Subscriptionが必要ですが、MolesのみならばFreeです。VS Galleryに置かれているので、VSの拡張機能マネージャーからでも検索に引っかかります。
Moles。Pex and Molesとして、つまりPex(パラメータ自動生成テスト)のオマケですよねー、と考えていたりしたりした私ですが(実際、Pexがこの種のモックシステムを必要とする、という要請があって出来た副産物のよう)、これがオマケだなんてとんでもない!アセンブリ解析+DLL自動生成+ILジャックという、吹っ飛んだ発想による出鱈目すぎる魔法の力でモック生成してしまうMolesは、他のモックフレームワークとは根源的に違いすぎる。
Molesとは何か。既存のクラスの静的/インスタンスメソッドやプロパティの動作を、自由に置き換えるもの。既存のクラスとは、自分の作ったものは勿論、.NET Frameworkのクラスライブラリも例外ではありません。Console.WriteLineやDateTime.Now、File.ReadAllTextなども、そのままに乗っ取ることが可能です。PublicもPrivateも、どちらでも乗っ取れます。
しかも使うのは簡単。往々に強力なものは扱いも難しくなってしまうものですが、常識はずれに強力な魔法が働いている場合は、逆に非常に簡単になります。対象となるメソッドにラムダ式を代入する。それだけ。moqなどよりも遥かに簡単。
Molesを使う
日本語での紹介はMoles - .NETのモック・スタブフレームワーク - Jamzzの日々に、また、MSRのページのDocumentationにLevel分けされた沢山のドキュメントが用意されているので(素晴らしい!Rxも見習うべし!)、そちらに目を通せば大体分かると思われます。
とりあえず使ってみましょう。Molesをインストールしたら、テストプロジェクトを作って、参照設定を右クリックし、Add Moles Assembly for mscorlibを選択。
するとmscorlib.molesというファイル(中身はただのXML)が追加されます。そして、とりあえずビルドするとMicrosoft.Moles.Framework, mscorlib.Behavior, mscorlib.Molesが参照設定に追加されます。つまり、mscorlibが解析され、モッククラスが自動生成されました!mscorlib以外のものも生成したい場合は、参照設定の対象dll上で右クリックし、Add Moles Assemblyを選べば、.molesが追加されます。なお、解析対象が更新されてHoge.Molesも更新したい、という場合はリビルドすれば更新されます(逆に言えばリビルドしないと更新されないため、コンパイルは通るものの実行時エラーになります)。また、もし追加したことによって何かエラーが出る場合(VS2010 SP1で私の環境ではSystem.dllでエラーが発生する)は、.molesの対象アセンブリの属性にReflectionOnly=”true”も記載すると回避できることもあります。
では簡単な例を。
// mscorlibに含まれる型の場合のみ、Molesで乗っ取りたい型を定義しておく必要があります // 定義なしで実行すると、この型定義してね、って例外メッセージが出るので // それが出たらコピペってAssemblyInfo.csの下にでも書いておけばいいんぢゃないかな [assembly: MoledType(typeof(System.DateTime))] [TestClass] public class UnitTest1 { // 現在時刻を元に"午前"か"午後"かを返すメソッド public static string ImaDocchi() { return (DateTime.Now.Hour < 12) ? "午前" : "午後"; } // HostType("Moles")属性を付与する必要がある [TestMethod, HostType("Moles")] public void TestMethod1() { // ラムダ式で置き換えたいメソッドを定義する // プリフィックスMが自動生成されているクラス、 // サフィックスGetはプロパティのgetの意味 MDateTime.NowGet = () => new DateTime(2000, 1, 1, 5, 0, 0); ImaDocchi().Is("午前"); MDateTime.NowGet = () => new DateTime(2000, 1, 1, 15, 0, 0); ImaDocchi().Is("午後"); } }
お約束ごと(属性付与)が若干ありますが、エラーメッセージで親切に教えてくれるので、そう手間もなくMoles化出来ます。モック定義自体は何よりも簡単で、見たとおり、デリゲートで置き換えるだけです。非常に直感的。(IsはChaining Assertion利用のものでAssert.AreEqualです、この場合)
システム時刻に依存したメソッドのテストは、単体テストの書き方として、よく例に上がります。そのままじゃテスト出来ないのでリファクタリング対象行き。メソッドの引数に時刻を渡すようにするか、時刻取得を含んだインターフェイスを定義して、それを渡すとか、ともかく、共通するのは、外部から時刻を操れるようにすることでテスト可能性を確保する。ということ。
Molesを使えば、そもそもDateTime.Now自体をジャックして任意の値を返すように定義出来てしまいます。これは単純な例でしかないので、いくら出来てもそんなことやらねーよ、かもですね。はい。それが良い設計かどうかは別としても、Molesの存在を前提とすると、テスト可能にするための設計方法にも、かなりの変化が生じるのは間違いないでしょう。時に、テスト可能性のために歪んだ設計となることも、Molesで乗っ取れるのだと思えば、自然な設計が導出できるはず。
イベントのモック化
続けてイベントの乗っ取りも画策してみましょう。イベントの乗っ取りは、正直なところ少し面倒です。
// こんな非同期でダウンロードして結果を表示するメソッドがあるとして public static void ShowGoogle() { var client = new WebClient(); client.DownloadStringCompleted += (sender, e) => { Console.WriteLine(e.Result); }; client.DownloadStringAsync(new Uri("http://google.co.jp/")); } [TestMethod, HostType("Moles")] public void WebClientTest() { // 外から発火出来るように外部にデリゲートを用意 DownloadStringCompletedEventHandler handler = (s, e) => { }; // AddHandlerとRemoveHandlerを乗っ取って↑のものに差し替えてしまう MWebClient.AllInstances.DownloadStringCompletedAddDownloadStringCompletedEventHandler = (wc, h) => handler += h; MWebClient.AllInstances.DownloadStringCompletedRemoveDownloadStringCompletedEventHandler = (wc, h) => handler -= h; // DownloadStringAsyncをトリガに用意したデリゲートを実行 MWebClient.AllInstances.DownloadStringAsyncUri = (wc, uri) => { // DownloadStringCompletedEventArgsはコンストラクタがinternalなので↓じゃダメ // handler(wc, new DownloadStringCompletedEventArgs("google!modoki")); // というわけで、モックインスタンス作ってしまってそれを渡せばいいぢゃない var mockArgs = new MDownloadStringCompletedEventArgs() { ResultGet = () => "google!modoki" }; handler(wc, mockArgs); }; // 出力はConsole.WriteLineなので、それを乗っ取って、結果にたいしてアサート MConsole.WriteLineString = s => s.Is("google!modoki"); ShowGoogle(); // 準備が終わったので、実行(本来非同期だけど、全て同期的処理に置き換えられてます) }
ちょっと複雑です。テストしたい処理はDownloadStringCompletedの中ですが、外からこれを発火する手段は、ない。この例だとAddHandlerだけ乗っ取って、直に発火させてもいいのですが、(非同期だけじゃなく)他のイベントの場合でも応用が効くように、正攻法(?)でいきましょう。イベントの発火を自分でコントロール出来るように、まずはAddとRemoveに対し、外部デリゲートに通すよう差し替えます。なお、インスタンスメソッドを乗っ取る場合は.AllInstancesの下にあるインスタンスメソッドを、静的メソッドと同じようにラムダ式で直に書き換えるだけです。非常に簡単。なお、第一引数は必ず、そのインスタンス自身となっていることには注意。
あとは、トリガとなるメソッドがあればそれを(この場合はDownloadStringAsync)通して、そうでない場合(例えばただのボタンクリックとか)なら直にイベントを乗っ取ったデリゲートを発火してやれば完了。で、ここでEventArgsがコンストラクタがprivateなせいで生成出来なかったりというケースも少なくないのですが、それはモックインスタンスを作って、そいつを渡してやるだけで簡単に回避できます。
少し手順が多いですが、「出来る」ということと、まあ流れ自体は分かるしそれぞれは置き換えるだけで複雑じゃない。ということは分かるのではと思います。でも、それでも面倒くさいですよ。ええ、どう見ても面倒くさいです。しかし、このことはReactive Extensionsを使えば解決出来ます。んが、その前にもう一つ別の例を。
APMのモック化
非同期繋がりで、APM(Asynchronous Programming Model, BeginXxx-EndXxx)のモック化もやってみましょう。
// こんな非同期でダウンロードして結果を表示するメソッドがあるとして public static void ShowBing() { var req = WebRequest.Create("http://bing.co.jp/"); req.BeginGetResponse(ar => { var res = req.EndGetResponse(ar); using (var stream = res.GetResponseStream()) using (var sr = new StreamReader(stream)) { var result = sr.ReadToEnd(); Console.WriteLine(result); } }, null); } [TestMethod, HostType("Moles")] public void WebRequestTest() { // Beginでコールバックを呼ぶ、EndでWebResponseを返す MHttpWebRequest.AllInstances.BeginGetResponseAsyncCallbackObject = (req, ac, obj) => { ac(null); return null; }; MHttpWebRequest.AllInstances.EndGetResponseIAsyncResult = (req, ar) => { return new MHttpWebResponse { GetResponseStream = () => new MemoryStream(Encoding.UTF8.GetBytes("bing!modoki")) }; }; MConsole.WriteLineString = s => s.Is("bing!modoki"); ShowBing(); // 実行 }
イベントよりは少し簡単ですが、BeginとEndの絡み具合は混乱してしまいます。また、HttpWebResponseのダミーを作るのも面倒。
Reactive Extensions
見てきたように、イベントもAPMも、モック化は面倒です。そこで出てくるのがReactive Extensions。RxならばIObservableとして一つにまとまるので、その一点をモック化してしまえばそれだけですむ、しかもダミーのIObservableを生成するのは非常に簡単!というわけで、例を見ましょう。モック化、の前に非同期のRx化と、そのテストを。
// こっち本体 public class Tweet { public string Name { get; set; } public string Text { get; set; } // 実際やるなら静的メソッドじゃなくて、API操作はまとめて別のクラスで、と思いますが、まあとりあえずこれで public static IObservable<Tweet> FromPublicTL() { var req = WebRequest.Create("http://twitter.com/statuses/public_timeline.xml"); return Observable.FromAsyncPattern<WebResponse>(req.BeginGetResponse, req.EndGetResponse)() .Select(r => { // StreamはSilverlightでも同期で書けるので、同期で取得しちゃいます using (var stream = r.GetResponseStream()) using (var sr = new StreamReader(stream)) { return sr.ReadToEnd(); } }) .SelectMany(s => XElement.Parse(s).Elements()) // 配列上のものをバラして .Select(x => new Tweet // Tweetに変換 { Text = x.Element("text").Value, Name = x.Element("user").Element("screen_name").Value }); } } // こっちがTest [TestMethod] [Timeout(3000)] // Timeoutはテスト全体のオプションで設定してもいいね public void FromPublicTL() { var tl = Tweet.FromPublicTL().ToEnumerable().ToArray(); // 20件あって、NameとかTextが // 全部空じゃなければ正常にParse出来てるんじゃないの、的な(適当) tl.Length.Is(20); tl.All(t => t.Name != "" && t.Text != "").Is(true); }
Twitterのpublic_timeline.xml、つまり認証のかかってない世界中のパブリックなツイートが20件(オプション無しの場合)XMLで取れるAPIを叩いています。RxのFromAsyncPatternを使い、リクエストは非同期。非同期のテストは通常難しい、のですが、Rxの場合はFirstやToEnumerableで簡単にブロックして同期的なものに変換出来るため、それで結果を取って、何食わぬ顔でアサートしちゃえます。
Rxは非同期が簡単にテスト出来てメデタシメデタシ。これはこれで良いのですが、ところでパブリックじゃなくて認証入るものを取るときはどうするの?ストリーミングAPI(ツイートだけじゃなくFavoriteなど色々な形式のXMLが届く)を試したいけど、誰かがFavoriteつけるまで待機とか、テストに不定な時間がかかるものはどうするの?などなどで、本物のウェブ上のデータをテストで毎回取ってくるのは大変です。また、誤ったデータが流れてきた/サーバーが応答不能状態な場合などの例外処理のテストは、通常では出来ないですね?
そこで、モック。ウェブからじゃなくてモックがダミーのデータを返せばいいわけだ。そして改めてFromPublicTLメソッドを見ると「データ取得」と「データパース」の二つを行っている。なので、ここはその二つに分けて、後者の「データパース」がモックでテスト出来るようにしてやりましょう。
public class Tweet { public string Name { get; set; } public string Text { get; set; } private static IObservable<String> GetRawPublicTL() { var req = WebRequest.Create("http://twitter.com/statuses/public_timeline.xml"); return Observable.FromAsyncPattern<WebResponse>(req.BeginGetResponse, req.EndGetResponse)() .Select(r => { using (var stream = r.GetResponseStream()) using (var sr = new StreamReader(stream)) { return sr.ReadToEnd(); } }); } public static IObservable<Tweet> FromPublicTL() { return GetRawPublicTL() .SelectMany(s => XElement.Parse(s).Elements()) .Select(x => new Tweet { Text = x.Element("text").Value, Name = x.Element("user").Element("screen_name").Value }); } }
リファクタリングというほど大仰なものでもなく、メソッドチェーンのうちのネットワークアクセス部分をprivateメソッドとして切り出しただけです。Rxは、メソッドチェーンの一つ一つが独立しているので、切った貼ったが簡単なのもメリット。では、このprivateメソッドをMolesで差し替えてしまおう!
[TestMethod, HostType("Moles")] public void FromPublicTLMock() { // これは省略した文字列ですが、実際は取得したXMLをファイルに置いて、それを読み込むといいかも var statuses = @" <statuses> <status> <text>Hello</text> <user> <screen_name>neuecc</screen_name> </user> </status> <status> <text>Moles</text> <user> <screen_name>xbox99</screen_name> </user> </status> </statuses> "; // 本来ネットワーク取得のものを、たった一行でただのシーケンスに置き換える MTweet.GetRawPublicTL = () => Observable.Return<string>(statuses); var tl = Tweet.FromPublicTL().ToEnumerable().ToArray(); tl.Length.Is(2); tl[0].Is(t => t.Name == "neuecc" && t.Text == "Hello"); tl[1].Is(t => t.Name == "xbox99" && t.Text == "Moles"); }
これだけです。データ用意は別として、モックへの差し替えはたった一行書いただけ。既存のコードに一切手を加えず、こんなにも簡単にモックへの置き換えが可能だなんて、わけがわからないよ。
理由として、Rxの持つ非同期もイベントも普通のシーケンスも、全て等しく同じ基盤に乗っている、という性質が生きています。この性質は時に分かりづらさを生むこともありますが、しかしそれ故に絶大な柔軟性も持っていて、その結果、本来非同期処理のものをただのシーケンスに置き換えることを可能にしています。非同期が、イベントがテストしづらいならMolesでただのシーケンスに差し替えてしまえばいい。別段「テストのため」の設計を意識しなくても、Rxで書くということ、それだけで自然にテスト可能な状態になっています。
なんて、さらっと流してしまっているわけですが、この事に気づいた瞬間にこれはヤバい!と悶えました。いや、凄いよ、凄過ぎるよRx + Moles。
Moq vs Moles、あるいは検証のやり方
Molesは非常に強力ですが、ではMoqと、どう使いわけよう?もしくは、全て代替出来てしまう?Molesは純粋な置き換えのみなので、呼び出しの検証はありません。モックとスタブの用語の違い、を言うならば、Molesの提供するものはモックではなくスタブ。自動生成クラスにつくプリフィックスのSは勿論Stubですが、MはMockではなく、Moleを指します(じゃあMoleって何よ、っていうと、何なんでしょうね……)
さて、使い分けとかいうほどのものでもないので、基本はMolesのみでいいんじゃないかなあー。もし呼び出しを保証したければ、こういうふうに書ける。
// IDisposableのDisposeは1回しか呼ばれないとしたい場合を検証する // インターフェイスの場合はMHogeではなくSHogeなことに注意 var callCount = 0; var mock = new SIDisposable() { Dispose = () => { callCount += 1; } }; (mock as IDisposable).Dispose(); // mockを使った処理があるとする... callCount.Is(1); // 1回のみ
フレームワークに用意されていないから一手間なのは事実ですが、Molesの持つシンプルさを失ってまで足したいほどでもなく、好きなようなチェックを自前で書けるのだから、それでいいかな。むしろこのほうが大抵スッキリ。といったようなことは、MolesのマニュアルのComparison to Existing Frameworksに書かれています。Molesの提供するシンプルさが、私は好きです。
フレームワークは最大限のシンプルさを保って、機能は他の機構に回すというのはChaining Assertionも一緒ですよ←比較するとはなんておこがましい
もう一つ、もっと具体的なもので行きましょうか。LinqのCount()はICollectionの場合は全部列挙せず、Countプロパティのほうを使ってくれる(詳細は過去記事:LinqとCountの効率をどうぞ)ことのテスト。
var countCalled = false; var enumeratorCalled = false; var mock = new SICollection01<int> { CountGet = () => { countCalled = true; return 100; }, GetEnumerator = () => { enumeratorCalled = true; return null; } }; // 呼んでるのはLinqのCount()のほうね mock.Count().Is(100); countCalled.Is(true); enumeratorCalled.Is(false);
CountGetで100返せば、それだけでいい気もしますが、念のため+意図を表明するということで。
そういえばですが、Chaining AssertionのIsは、散々DisったAssertThatに存外近かったりします。 Assert.That(actual, Is(expected)) と書くものを、 actual.Is(expected) と書けるようになった、ですから(但しこれはAreEqualsの場合であって、Shuold.Be.GreaterThanとかやり始めたらぶん殴る)。
Silverlight? Windows Phone 7?
Silverlightのテスト環境は貧弱です。当然それに連なってWP7のテスト環境も貧弱です。というかMSTestが使えない!というだけじゃなく、Molesも動かせませんし。どうする?そこは、「リンクとして追加」でSilverlight/WP7のファイルをWPFのプロジェクトにでも移して、そのWPFのコードをテストするという手段が取れなくもないです。非同期周りはRxが吸収出来るし、互換性は、元来クラス群が貧弱なSLのほうが第一ターゲットなので、まあまあ大丈夫なはず。ViewModelはともかくとして、Modelのテストなら行けるはずです。
非同期のテストは難しいって?うん、Rxを使えば簡単なんだ。大丈夫。
まとめ
次世代というか、もう現世代なんですよ。今まで理想論に過ぎなかったものを、急速に現実のものとしてくれています。徒手空拳では難しい領域はいっぱいあった。でも、今、手元にはRxとMolesがある。この二つを手に、もう一度領域を見てみたらどうだろう?晴れた景色が広がっているはずです。
それにしてもRxの素晴らしさがMolesで更に輝くことといったらない。
今回はRxと組み合わせた例を中心に説明しましたが、Molesは単体でも文句なく素晴らしい。Moqも悪くないけれど、選ぶならMolesです。とにかく抜群に使いやすい。機能が極まっていることと、APIのシンプルさは両立するんだって。自動生成を活かしきった事例ですねー。VSとのシームレスな一体化といい、文句のつけようがない。ついこないだまで軽視していた私が言うのもアレですが、これがそんなに知られていない(少なくともググッて引っかかる記事はid:jamzzさんの記事だけだ)のは勿体無い話。Moles、是非試してみてください。
Rx FromEvent再訪(と、コードスニペット)
最近の私ときたらFromAsyncでキャッキャウフフしすぎだが、Asyncの前にEventを忘れているのではないか、と。というわけで、FromEventについて、「また」見直してみましょう!延々と最初の一歩からひたすら足踏みで前進していないせいな気はその通りで、いい加減飽きた次に進めということだけれど、まあそれはそのうち。私的にはFromEventはとうに既知の話だと思い込んで進めているのであまり話に出していなかっただけなのですが、初期に出したっきりで、特にここ数カ月で(WP7出たりAsync CTP出たり昇格したり)でRxが注目を浴びるのが多くなってはじめましてな場合は、そんな昔の記事なんて知らないよねですよねー(Blogの形式は過去記事へのポインタの無さが辛い)。なわけなので定期再び。
「Rxの原理再解説」や「時間軸という抽象で見るRx」、というネタをやりたいのですが、長くなるのと絵書いたり動画撮ったり色々準備がという感じで中々書き進められていないので、先にFromEvent再訪を。
4オーバーロード
FromEventはイベントをReactive Sequenceに変換するもの。これはオーバーロードが4つあります。
// ただのEventHandlerを登録する場合はハンドラの+-を書くだけ // 例はWPFのWindowクラスのActivatedイベント Observable.FromEvent(h => this.Activated += h, h => this.Activated -= h);
使う機会は少ないかな? 実際はEventHandler/EventArgsだけのものなどは少ないわけで。
// WPFのButtonなどり Observable.FromEvent<RoutedEventArgs>(button1, "Click");
これはサンプルなどで最も目にすることが多いかもで、文字列でイベントを登録するもの。記述は短くなるのですが、動作的にはイベント登録時にリフレクションで取ってくることになるので、あまり推奨はしない。じゃあどうすればいいか、というと
// 第一引数conversionはRoutedEventHandlerに変換するためのもの、とにかく記述量大すぎ! Observable.FromEvent<RoutedEventHandler, RoutedEventArgs>( h => h.Invoke, h => button1.Click += h, h => button1.Click -= h);
ハンドラの+-を自前で書くわけですが、EventArgsと一対一の俺々EventHandlerへの変換関数も必要になっています。これはnew RoutedEventHandler() などとしなくても、 Invoke と書くだけで良いようです。最後のオーバーロードは
// EventHandler<T>利用のものって本当に少ないんですよね、こちらを標準にして欲しかった Observable.FromEvent<TouchEventArgs>(h => button1.TouchDown += h, h => button1.TouchDown -= h);
EventHandler<T>のものはスッキリ書けます。
コードスニペット
conversionが必要なFromEvent面倒くさい。それにしても面倒くさい。WPFのINotifyPropertyChangedほどじゃないけれど、やはり面倒くさい。ジェネリックじゃない俺々EventHandlerどもは爆発しろ!デリゲートはEventHandler<T>とFuncとActionがあれば他は原則不要(ref付きが必要とか、そういう特殊なのが欲しい時に初めて自前定義すればよろし)。と、嘆いてもしょうがない。何とかしなければ。以前はT4でガガガガッと自動生成してしまう方法を紹介しましたが、少し大仰な感があります。もう少しライトウェイトに、今度は、コードスニペットでいきましょう。
// 普通に使うもの Observable.FromEvent<$EventHandler$, $EventArgs$>(h => h.Invoke, h => $event$ += h, h => $event$ -= h) // 拡張メソッドとして定義する場合のもの public static IObservable<IEvent<$EventArgs$>> $eventName$AsObservable(this $TargetType$ target) { return Observable.FromEvent<$EventHandler$, $EventArgs$>( h => h.Invoke, h => target.$eventName$ += h, h => target.$eventName$ -= h); }
この二つです。二つ目の拡張メソッドのものは、ええと、大体の場合は長ったらしくて面倒なので拡張メソッドに退避させるわけですが、それを書きやすくするためのものです。利用時はこんな形。
class Program { static void Main(string[] args) { var c = new ObservableCollection<int>(); var obs1 = Observable.FromEvent<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>(h => h.Invoke, h => c.CollectionChanged += h, h => c.CollectionChanged -= h); var obs2 = c.CollectionChangedAsObservable(); } } public static class EventExtensions { public static IObservable<IEvent<NotifyCollectionChangedEventArgs>> CollectionChangedAsObservable<T>(this ObservableCollection<T> target) { return Observable.FromEvent<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>( h => h.Invoke, h => target.CollectionChanged += h, h => target.CollectionChanged -= h); } }
rxevent -> TabTab か、 rxeventdef -> TabTab というだけで、この面倒くさい(長い!)定義が簡単に書けます。おっと、スニペットファイルはXMLなのでこれだけじゃ動きませんね。ベタ張りだと長いので、ダウンロードはneuecc / RxSnippet / source – Bitbucketの二つからどうぞ。そうしたら、ツール->コードスニペットマネージャーで追加してやってください。
今回、スニペットはSnippet Designerで作成しました。Visual Studioと統合されているので非常に書きやすくてGood。コンパイルがしっかり通る、万全な雛形をコード上で作ったら右クリックしてExport As Snippet。スニペットエディタ上に移ったら、置換用変数を選択してMake Replacement。それだけで出来上がり。あとはプロパティのDescriptionとShortcutを書くだけ。楽すぎる。もうスニペットを手書きとか馬鹿らしくてやってられません。これだけ楽だと、ちょっとした面倒事を片っ端からスニペット化していけるというものですねん。
アタッチ、デタッチのタイミングを考えよう
Rxで意外と困るのが、いつアタッチされているのか、デタッチされているのか、よくわからなかったりします。慣れると分かってくるのですが、最初は存外厳しい。そういう時は悩まずに、デバッガを使おう!
こういったように、アタッチのラムダ式、デタッチのラムダ式にそれぞれ縄を張れば、いつ呼ばれるのか一目瞭然です。悩むよりも手を動かしたほうがずっと早い。
ところでRxのいいところは、イベントのデタッチが恐ろしく簡単になったことです。そのお陰で今まであまりやらなかった、そもそも考えすらしなかった、アタッチとデタッチを繰り返すようなイベント処理の書き方が発想として自然に浮かび上がるようになった。少し簡単に書けるようになった、という程度じゃあそんな意味がない。Rxのように極限まで簡単に書けるようになると、スタイルが一変して突然変異が発生する。ある意味これもまた、パラダイムシフトです。オブジェクト指向から関数型へ?いえいえ、C#から、Linqへ。
まとめ
2/11にRx v1.0.2856.0 releaseとしてアップデートが来てました。大量更新ですよ大量更新!正式入りしたから更新ペースがゆったりになるかと思いきや、その逆で加速しやがった!ちなみに破壊的変更も例によって平然とかけてきて、Drainというメソッドが消滅しました(笑) 何の躊躇いもないですね、すげー。
代わりに、Christmas Releaseの時に消滅してWP7と互換がなくなった!と騒いだPruneとReplayは復活しました(なんだってー)。というわけで、再びWP7との互換は十分保たれたという形ですね、ヨカッタヨカッタ。そんなわけで、常に見張ってないと分からないエキサイティングさが魅力のRx、是非是非使っていきましょう。
