XboxInfoTwit - ver.2.3.0.1
- 2010-10-21
Xbox.comがリニューアルされました!というわけで新Xbox.comに対応しました。それだけです!そして、Xbox.comの構造が変わったお陰で取得が軽量化されました(情報取得のための巡回ページ数が大幅削減されたため)。ヨカッタヨカッタ。あまり良い評判を聞かない新Xbox.comですが、こういうところで良くなってますよー、と。地味な変化としては、今回からフレンドが0な場合でも取得/投稿できるようになりました。たまにフレンドいないから使えない!という嘆きを見かけるので、ちゃんと使えるようになってヨカッタヨカッタ。
例によってテストが非常に不十分なため、ゲームタイトルによっては投稿できなかったりするかもしれません。もし怪しいところがあったら、Twitterで投稿文に「XboxInfoTwit」を含めてポスト、例えば「GoW2がXboxInfoTwitで動かないんだけど何これ」とか言ってもらえれば、検索経由で発見しますので気楽に文句書いてください。(GoW2は動きますけど)
AnonymousComparer - ver.1.3.0.0
- 2010-10-18
ver 1.3というか、バグフィックスです。ToDictionaryとかだと拡張メソッドが本来あるのとオーバーロードが被ってて使おうとするとコンパイル通らなかったのです。気づいてたんだけど放置してました、すみませんすみません。というわけで幾つかのものを削りました。これでコンフリクトなし。
どんなものかというと、こんなものです。最初投稿した時のの流用で(こらこら)
class MyClass
{
public int MyProperty { get; set; }
}
static void Main()
{
// 例として、こんな配列があったとします
var mc1 = new MyClass { MyProperty = 3 };
var mc2 = new MyClass { MyProperty = 3 };
var array = new[] { mc1, mc2 };
// Distinctは重複を取り除く。でも結果として、これは、2です。
var result = array.Distinct().Count();
// 参照の比較なので当然です。では、MyPropertyの値で比較したかったら?
// DistinctにはIEqualityComparerインスタンスを受け付けるオーバーロードもあります
// しかしIEqualityComparerはわざわざ実装したクラスを作らないと使えない
// そこで、キー比較のための匿名Comparerを作りました。
// ラムダ式を渡すことで、その場だけで使うキー比較のIEqualityComparerが作れます。
array.Distinct(AnonymousComparer.Create((MyClass mc) => mc.MyProperty));
// でも、長いし、型推論が効かないから型を書く必要がある
// Linqに流れているものが匿名型だったりしたら対応できないよ!
// というわけで、本来のLinqメソッドのオーバーロードとして、記述出来るようにしました
// ちゃんと全てのIEqualityComparerを実装しているLinq標準演算子に定義してあります
array.Distinct(mc => mc.MyProperty);
// 短いし、型推論もちゃんと効くしで素晴らしいー。
// 匿名型でもいけます(VBの匿名型はC#(全ての値が一致)と違ってKey指定らしいですね)
var anonymous = new[]
{
new { Foo = "A", Key = 10 },
new { Foo = "B", Key = 15 }
};
// true
anonymous.Contains(new { Foo = "dummy", Key = 10 }, a => a.Key);
}
つまり、LinqのIEqualityComparerのオーバーロードうぜえ、何がインターフェースだよクソが、Linqならラムダ式だろ、インターフェースとかJava臭いんだよ。無名クラス(別に欲しくはないけど)がないから作るの面倒なんだよ。ということです。あるとそれなりに便利です。
Windows Phone 7で同期APIを実現するたった つの冴えないやり方
- 2010-10-14
Windows Phone 7が発表されました。中々に素晴らしい仕上がりに見えます。米国では来月発売と非常に順調そうですが、日本では…… ローカライズが非常に難しそうに見えました。発売されること自体は全然疑っていませんが、問題は、米国で達成出来ているクオリティをどこまで落とさず持ってこれるか。日本語フォントや日本語入力、今一つなBing Map、但し日本は除くなZune Pass。本体だけではなく、周辺サービスも持ってきて初めてWindows Phone 7の世界が完成する。ということを考えると、大変難しそう。
その辺はMicrosoft株式会社に頑張ってもらうとして、一開発者的には淡々とアプリ作るだけでする。というわけで、標題のお話。WP7というかSilverlightと、そして例によっていつもの通り、Rxの話です。
問題です。以下のコードの出力結果(Debug.WriteLineの順序)はどうなるでしょうか。
// 何も変哲もないボタンをクリックしたとする
void Button_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("start");
// 10秒以内にレスポンスが来るとする
var req = WebRequest.Create("http://bing.com/");
req.BeginGetResponse(ar => Debug.WriteLine("async"), null);
Thread.Sleep(10000); // 10秒待機
Debug.WriteLine("end");
}
答えは後で。
Dispatcher.BeginInvokeとPriority
Dispatcherとは何ぞやか。について説明するには余白が狭すぎる。ので軽くスルーしてコードを。Dispatcher.BeginInvokeは通常は別スレッドから単発呼び出しが多いですが、UIスレッド上でDispatcher.BeginInvokeを呼ぶとどうなるでしょう?
// (WPF)何も変哲もないボタンをクリックしたとする
void Button_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("start");
Dispatcher.BeginInvoke(new Action(() => Debug.WriteLine("normal1")), DispatcherPriority.Normal);
Dispatcher.BeginInvoke(new Action(() => Debug.WriteLine("background")), DispatcherPriority.Background);
Dispatcher.BeginInvoke(new Action(() => Debug.WriteLine("normal2")), DispatcherPriority.Normal);
Debug.WriteLine("end");
}
結果は、start->end->normal1->normal2->backgroundです。なおDispatcherPriorityはWPFでは設定可能ですが、Silverlightでは設定不可で、内部的には全てBackgroundになります。挙動は以下の図のようになっています。
一番上のブロックが現在実行中メソッド。下のがDispatcher。BeginInvokeで実行キューに優先度付きで突っ込まれて、現在実行中のメソッドが終了したら、キューの中のメソッドが順次、優先度順に実行されます。といったイメージ。
問題の答え
冒頭の問題の答えは、WPFではstart->async->endの順。Silverlight(WP7も含む)ではstart->end->asyncの順になります。ええ。WPFとSilverlightで挙動が違うのです!今更何をっていう識者も多そうですが(Silverlightももう4だしねえ)私ははぢめて知りました。はまった。BeginGetResponseはWPFでは(というか普通の.NET環境では)そのまま別スレッド送りで実行されますが、Silverlightでは一旦Dispatcherに突っ込まれた後に実行されるのですねー、といったような雰囲気(なので一つ前でDispatcher.BeginInvokeがどうのという話を挟みました)。
Silverlightでは、BeginGetResponseはすぐには実行されない。それを踏まえて次へ。
非同期 to 同期
非同期を同期に変換してみましょう。Reactive Extensions for .NET (Rx)で。
void Button_Click(object sender, RoutedEventArgs e)
{
// 非同期を同期に変換!
var req = WebRequest.Create("http://bing.com/");
var response = Observable.FromAsyncPattern<WebResponse>(req.BeginGetResponse, req.EndGetResponse).Invoke()
.First();
Debug.WriteLine(response.ResponseUri);
}
Rxで非同期を包むと長さ1のReactiveシーケンスとなるので、Firstを使うと同期的に値を取り出せる、という話を前回の記事 Rxを使って非同期プログラミングを簡単に でしました。そして実際、上のコードはWPFでは上手く動きます。きっちりブロックして値を取り出せる。勿論、それならGetResponseを使えよという話ではありますが。
では、Silverlight(勿論WP7でも)では、というと…… 永久フリーズします。理由は、BeginGetResponseはDispatcherに積まれた状態なので、現在実行中のメソッドを抜けない限りは動き出さない。Firstは非同期実行が完了するまでは現在実行中のメソッドで待機し続けるので、結果として、待機しているので実行が始まらない=実行完了は来ない→永遠に待機。になります。
結論としては、UIスレッド上で同期的に待つことは不可能です。代替案としてはThreadPoolで丸々包んでしまうということもなくはない。
void Button_Click(object sender, RoutedEventArgs e)
{
ThreadPool.QueueUserWorkItem(_ =>
{
var req = WebRequest.Create("http://bing.com/");
var response = Observable.FromAsyncPattern<WebResponse>(req.BeginGetResponse, req.EndGetResponse)
.Invoke()
.First();
Debug.WriteLine(response.ResponseUri);
});
}
こうすれば、BeginGetResponseが発動するので問題なく待機して値を取り出せます。でも、これじゃあ全然嬉しくもない話で全く意味がない。Rxで包んでいる状態ならば、.Subscribeでいいぢゃん。ということだし。
// 非同期を同期に、そんなことは幻想なのでこう書くのがベストプラクティス
void Button_Click(object sender, RoutedEventArgs e)
{
var req = WebRequest.Create("http://bing.com/");
Observable.FromAsyncPattern<WebResponse>(req.BeginGetResponse, req.EndGetResponse)
.Invoke()
.Subscribe(res => Debug.WriteLine(res.ResponseUri));
}
素直に、普通にReactive Extensionsを使うのが、一番簡単に書けます。息を吸うように、ごく自然にそこにあるものとしてRxを使おう。
Delegate.BeginInvokeのこと
相違点はまだあります。普通の.NET環境ではDelegateのBeginInvokeで非同期実行できますが、Silverlightにはありません。やってみるとNotSupportedExceptionが出ます。じゃあFuncにラップしてみるとどうだろう?
void Button_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("start");
Action action = () => Debug.WriteLine("action");
Func<AsyncCallback, object, IAsyncResult> wrappedBeginInvoke = action.BeginInvoke;
wrappedBeginInvoke.Invoke(ar => Debug.WriteLine("async"), null);
Debug.WriteLine("end");
}
WPFではstart->end->action->async。Silverlightではstart->NotSupportedExceptionの例外。ここまではいいんです。Windows Phone 7でこのコードを試すと、例外出ません。何故か実行出来ます。SilverlightではBeginInvokeは出来ないはずなのに!そして、その実行結果はstart->action->end。つまり、非同期じゃない。BeginInvokeじゃない。Invokeとして実行されてる。意味がさっぱりわかりません。
不思議!不思議すぎたので、MSDNのWindows Phone 7 Forumで聞いてみましたが、良い返答は貰えず。とりあえず、怪しい挙動をしているのは間違いないので、これはやらないほうが無難です。勿論、通常こんなこと書きはしないと思うのですが、RxのFromAsyncPatternをDelegateに対して使おうとするとこうなりますので注意。Delegateの非同期実行したい場合はFromAsyncPetternじゃなくてToAsyncを使いましょう。
void Button_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("start");
// Observable.ToAsync(()=>{})でもいいし、すぐにInvokeするならObservable.Start(()=>{})も有用
Action action = () => Debug.WriteLine("action");
action.ToAsync().Invoke().Subscribe(_ => Debug.WriteLine("async"));
Debug.WriteLine("end");
}
こうすることで、Rxは内部でBeginInvokeではなくThreadPoolを使うので、問題は起こらずWPFと同じ結果が得られます。
まとめ
同期的に書くほうが分かりやすいには違いないし、また、非同期が苦痛なのもその通り。でも、非同期を同期に、なんて考えない方がいい。AutoResetEventなどを駆使して擬似的に再現出来たとしても、やっぱ無理ありますし、非同期のメリットを犠牲にしてまでやるものではない。確かに非同期をそのまま扱うのは苦痛だけれど、Rxを使えば緩和される。むしろ慣れれば同期的に書くよりも利点が見えてくるぐらい。無理に同期に変換しようとしないでRxを覚えよう。が、結論です。
でもドキュメント全然ないし日本語の話なんて皆無で難しいって? そうですねえ、そうかもですねえ……。このブログも全然順序立ってなくて、思い立ったところから書いてるだけで分かりづらいことこの上ないし。うむむ……。でも、Rxの機能のうち非同期周りの解説に関してはほとんど出せているはずなので、読みにくい文章ですが、目を通してもらえればと思います。
もしつまづくところがあれば、Twitterで「Reactive Extensions」を投稿文に含めてくれれば、Twitter検索経由で見つけて反応します。(「Rx」だと検索結果が膨大になるので反応出来ません……)。検索を見てるワードとしては、他に「Linq」なども高確率で反応しにいきます←逆に怖いって?すみませんすみません。
「C#」が検索キーワードに使えたらいいんですけどねえ。「Scala」とか「JavaScript」は常時見てるんですが、かなり活況に流れているんですよ。そういうの見てると、Twitter上のC#な話も漏らさず見たい・参加したいと思ってしまうわけで。
XboxInfoTwit - ver.2.2.0.4
- 2010-10-14
未知のエラーが出まくっていたので、暫定というか適当な対処を取ってみました。コードがかなり古くて汚いので、根本的に手を入れたいところなんですが、作業量を考えると中々やる気が沸かないという微妙な状態。機能追加のリクエストも数点頂いているので申し訳ないんですけどね。
Rxを使って非同期プログラミングを簡単に
- 2010-10-09
こないだ公開されたMSDNマガジンの記事、非同期タスク - タスクを使って非同期プログラミングを簡単に。おお、これは分かりやすく非同期周りについて網羅されてる!あと、私はTask全然知らないので初歩から入る導入はお役立ちです。いやまあ、実際のとこTask周りの導入記事っていっぱいあるのに、未だにお役立ち、とか言ってるのもどうかと思わなくもないところではあるんですが不勉強なもので。
同期処理でUIをブロックしてしまう、スレッドプールに投げればいいぢゃない、イベントベースのパターンとAPM(IAsyncResultを使うAsynchronous Programming Model)、そしてTask。おお、全部ですね。全部、全部?そう、何か欠けてます、ええ、Reactive Extensionsが欠けています。というわけで、Taskと対比させながらRxでのコードを見ていきましょう。
非同期実行、そして待機
MSDNマガジンでは真ん中辺りからのタスクのコードを、Rxでのコードと一緒に並べてみます。
// タスクパターン
Task<double> task = Task.Factory.StartNew(() =>
{
double result = 0;
for (int i = 0; i < 10000000; i++)
result += Math.Sqrt(i);
return result;
});
Console.WriteLine("The task is running asynchronously...");
task.Wait(); // 実行完了まで待機
Console.WriteLine("The task computed: {0}", task.Result);
// Reactive Extensions
var obs = Observable.Start(() =>
{
double result = 0;
for (int i = 0; i < 10000000; i++)
result += Math.Sqrt(i);
return result;
});
Console.WriteLine("Observable.Start非同期実行中");
var r = obs.First(); // 結果が返るまで待機
Console.WriteLine("完了 : {0}", r);
// 余談:タスクはIObservableに変換出来たりする
task.ToObservable().Run(Console.WriteLine);
どちらもデフォルトではThreadPoolで非同期を実行します。ThreadPoolと違うのは、待機するのも戻り値を取り出すのも簡単。Rxでは長さ1のReactiveシーケンスとして扱われるので、Firstを使うと同期的にブロックして値を取り出せます。ここだけを見ると、Wait() + task.ResultなTaskより扱いやすいのではないかと思います。また、両者ともに似ているので、TaskからIObservable<T>への変換も容易です。System.Reactive.dllを読みこめば、Taskに対してToObservableメソッドが追加され、簡単に変換することが出来ます。
自由な変換
汎用的に非同期処理をTaskに、Rxに変換しよう。TaskにはTaskCompletionSourceが、RxにはAsyncSubjectがあります。
// Construct a TaskCompletionSource and get its
// associated Task
TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
Task<int> task = tcs.Task;
// Asynchronously, call SetResult on TaskCompletionSource
ThreadPool.QueueUserWorkItem(_ =>
{
Thread.Sleep(1000); // Do something
tcs.SetResult(123);
});
Console.WriteLine("The operation is executing asynchronously...");
task.Wait();
// And get the result that was placed into the task by
// the TaskCompletionSource
Console.WriteLine("The task computed: {0}", task.Result);
// ---
// TaskCompletionSourceは、RxではAsyncSubjectと対比させられる
// AsyncSubjectはRxでの非同期表現を自前で実装する場合に使う(Rx内部でも当然使われている)
var async = new AsyncSubject<int>();
ThreadPool.QueueUserWorkItem(_ =>
{
Thread.Sleep(1000); // 何か重い処理をしてたとする
async.OnNext(123); // 値のセット
async.OnCompleted(); // 値を確定し非同期実行完了
});
Console.WriteLine("重い処理を非同期で実行中...");
var r = async.First(); // 同期的に結果を待機し取得
Console.WriteLine("処理完了:{0}", r);
こちらもまた、両者ともに実によく似ています。何らかの任意の非同期処理は、使いやすいようにRxに包んでしまうと素敵な気分になれる。
IAsyncResultパターンの変換
IAsyncResultパターン。Rxの辺りでは、というか多分.NET周りで言う分には、Asynchronous Programming Model、略してAPMと呼ぶそうです。私がその言葉を見たのは、プログラミングMicrosoft .NET Framework 第2版 (マイクロソフト公式解説書)でした。Richterはリッチャーと呼ぶべきかリヒターと呼ぶべきなのか謎という話ががが。この本は.NET3種の良書のうちの一冊だと思うので(もう一冊は.NETのクラスライブラリ設計 開発チーム直伝の設計原則、コーディング標準、パターン (Microsoft.net Development Series)
、もう一冊は未定というか将来のために取っておくというか)だと思うので未読の人は是非是非。と思ったら絶版じゃないですか!いや、amazonで偶然品切れなだけかもしれませんが、どうなんでしょう。これが手に入らないのは損失です!海外では既に.NET 4に対応したThird Editionが出ています。ということは、であり、風のウワサによると―― らしいですので、まあ、ですね!
今読み返したら20ページほど費やされて色々書いてありました。ぶっちゃけ面倒くさいと思って半分以上流し読みしてたという事実に気づいてしまったり。おおぉ。まあ、その辺はThird Editionの時に拾い直せば……。そんなわけで、その面倒くささを緩和するFromAsync/FromAsyncPatternをどうぞ。
// FromAsyncで包むとその場で実行
Task<IPAddress[]> task = Task<IPAddress[]>.Factory.FromAsync(
Dns.BeginGetHostAddresses, Dns.EndGetHostAddresses, "www.microsoft.com", null);
task.Wait();
foreach (var item in task.Result) Console.WriteLine(item);
// RxのFromAsyncPatternの型指定は引数と戻り値の二つを指定する
// FromAsyncPatternで包んだら即実行ではなく、funcをInvokeするまでは開始されない
var obs = Observable.FromAsyncPattern<string, IPAddress[]>(Dns.BeginGetHostAddresses, Dns.EndGetHostAddresses)
.Invoke("www.microsoft.com"); // 即実行なら変数に代入せずメソッドチェーン、実行を遅らせたい場合はfuncで持っておくと良いかも
var r = obs.First();
foreach (var item in r) Console.WriteLine(item);
イベントベースのパターンの変換
残念なことに、Taskには組み込みの変換パターンがないので、TaskCompletionSourceを使って自前で作る必要があるようです。RxではAsyncSubjectを使って自前で用意するまでもなく、そもそもイベントのLinq化として売り出されたので、イベントベースのパターンの変換はお手の物です。見てみましょう。
var client = new WebClient();
Observable.FromEvent<DownloadStringCompletedEventArgs>(client, "DownloadStringCompleted")
.Select(e => e.EventArgs.Result)
.Subscribe(s => Console.WriteLine(s));
client.DownloadStringAsync(new Uri("http://www.microsoft.com/"));
Console.ReadKey(); // 完了待ち
// こういう風に書くとリフレクションを使わないので軽くて望ましい、けど結構面倒くさい(ので事前に自動生成しておくといいよ)
var obs = Observable.FromEvent<DownloadStringCompletedEventHandler, DownloadStringCompletedEventArgs>(
h => new DownloadStringCompletedEventHandler(h),
h => client.DownloadStringCompleted += h,
h => client.DownloadStringCompleted -= h);
client.DownloadStringAsync(new Uri("http://www.bing.com/"));
var result = obs.Select(e => e.EventArgs.Result).First(); // 同期で待機&受け取り
Console.WriteLine(result);
FromEvent。実に美しい!Linq!Linq!おっと、興奮してしまった。とはいえ、stringでイベント名を指定するのも、h=>hoge+=の連打も、どちらも悲しくダサい。そこでT4 TemplateでFromEventに包んだのを一気に自動生成してしまうのをお薦めします。そのためのT4は以前に書きましたので、Reactive ExtensionsのFromEventをT4 Templateで自動生成する 是非どうぞ。割と頑張ったし便利だと思ったけれど、はてブ数がああああ。まあ、そんなわけで、Rxをガリガリ使う分には必需品です。
しかし、それで大丈夫か?
FromEventの後者の書き方は問題を残しています。DownloadStringAsyncの後にFirst。これは、危険です。どう危険かというと……。
// T4自動生成を使うとFromEventがこんなスッキリ!
var client = new WebClient();
var obs = client.DownloadStringCompletedAsObservable();
// ところで、実行を開始した後にSubscribe(Firstもそうです)したら?
// それも、超速で非同期実行が完了したとしたら?
client.DownloadStringAsync(new Uri("http://www.bing.com/"));
Thread.Sleep(5000); // ダウンロードが完了した後にSubscribeする、をシミュレート
// 次の値は(完了済みなので)永遠にやってこない、つまり永久フリーズ
var result = obs.Select(e => e.EventArgs.Result).First();
Firstだと同期で延々と待つのでフリーズ。Subscribeならフリーズはありませんが、結果がこないので意図した結果ではないでしょう。これは大変マズい。そんな時はTake-Prune-Connectパターン、なんてものはなく今思いつきました。今思いついたのでこれがベストなやり方なのか、ちょっとよく分からないのであとでForumとか見て調べておきます。挙動的には全然問題ない。
// Take(1).Prune and ConnectでAsyncSubjectっぽい挙動に変換
var client = new WebClient();
var obs = client.DownloadStringCompletedAsObservable()
.Select(e => e.EventArgs.Result) // Selectはどこに書いてもいいので自分がスッキリと思うところへ
.Take(1)
.Prune();
obs.Connect();
client.DownloadStringAsync(new Uri("http://www.bing.com/"));
Thread.Sleep(5000); // ダウンロードが完了した後にSubscribeする、をシミュレート
var result = obs.First(); // 大丈夫だ、問題ない
Console.WriteLine(result);
var result2 = obs.First(); // 何度でも取り出せる
Console.WriteLine(result2);
わけわかんなくなってきました?失望のため息が聞こえます。どうしたものかねえ、これ。PruneはキャッシュとしてAsyncSubjectを持って後続に渡します。また、値を流すタイミングを自由に調整出来ます(Connectしたら流す、それまでは値が来ていても堰止める)。今回はFromAsyncPatternをなぞらえるため、即座にConnectしました。やっていることは、上の方で出したAsyncSubjectのパターンのシミュレーションです。つまり、OnNextが一回来て、OnCompletedが来る。そうでないと、AsyncSubjectが完了しない。FromEventはそのままだと無限リスト状態で完了の状態がこないので、Take(1)で長さ1のReactiveシーケンスとする。こうすることで、後ろに非同期結果の値が流れ出します。
といったイミフな話はReactive Extensionsの非同期周りの解説と自前実装で少し、それと、それに関連してufcppさんが分かりやすいスライドにしてまとめてくれていますので、必見 => さて、WordPress になったところで再度、PowerPoint 貼り付けテスト « ++C++; // 未確認飛行 C ブログ。貼りつけテストという実に分かりにくいタイトルでサラッと流してしまうところが漢らしい(謎)
タスクの操作と構成
一つの非同期実行程度なら、APMだろうがイベントモデルだろうが、素のまま扱っても別にそこまで面倒なわけではない。Taskが、Rxが真価を発揮するのは複数の操作を行うとき。まずは、待機を。
Task<int> task1 = new Task<int>(() => ComputeSomething(0));
Task<int> task2 = new Task<int>(() => ComputeSomething(1));
Task<int> task3 = new Task<int>(() => ComputeSomething(2));
task1.Start(); task2.Start(); task3.Start(); // 実行しとかないと永遠待機しちゃうよ
task1.Wait();
Console.WriteLine("Task 1 is definitely done.");
Task.WaitAny(task2, task3); // どっちかが完了するまで待機
Console.WriteLine("Task 2 or task 3 is also done.");
Task.WaitAll(task1, task2, task3); // 全部完了するまで待機
Console.WriteLine("All tasks are done.");
// ---
// Observable.Startは即時実行だけど、ToAsyncはInvokeまで実行開始されない
var async1 = Observable.ToAsync(() => ComputeSomething(0));
var async2 = Observable.ToAsync(() => ComputeSomething(1));
var async3 = Observable.ToAsync(() => ComputeSomething(2));
var io1 = async1(); // Invokeってのはデリゲートのなので()でもおk
var io2 = async2();
var io3 = async3();
io1.Run(); // 引数なしRunで実行結果も受けずただの待機になる
// WaitAnyはどちらか先に完了したほうを1つだけ流す Merge().Take(1) して待機
io2.Merge(io3).Take(1).Run();
Observable.Concat(io1, io2, io3).Run(); // WaitAllは全部連結して待機
Observable.ForkJoin(io1, io2, io3).Run(); // こちらは並列実行で待機
複数を同時に走らせて待機がいとも簡単に。で、面白いのがRx。Rxは非同期特化というわけではないので直接的にアレとコレのどっちかが来るまで待ってね、なんていうメソッドはないのですが、豊富な結合系メソッドで余裕でシミュレート出来てしまいます。WaitAnyはMerge.Takeで。WaitAllはConcatで。素晴らしい。凄い。と同時に、若干パズル的な気がしなくもない。が、しかし、面白い。Reactiveモデルの何でもできるという底力を感じる。
継続・継続・継続
今までは待機してたという、おいおい、非同期でやってるのに同期かよ、って感じだったので本領発揮で非同期のままの流るような実行を。TaskではContinueWith、Rxでは、72通りあるから何を言えばいいのか。
// ほう、メソッドチェーンが生きたな
Task<IPAddress[]>.Factory.FromAsync(Dns.BeginGetHostAddresses, Dns.EndGetHostAddresses, "www.microsoft.com", null)
.ContinueWith(t =>
{
foreach (var item in t.Result) // IPAddress[]なので。
{
Console.WriteLine(item);
}
});
// しかしRxはそれどころじゃない
Observable.FromAsyncPattern<string, IPAddress[]>(Dns.BeginGetHostAddresses, Dns.EndGetHostAddresses)
.Invoke("www.microsoft.com")
.SelectMany(xs => xs) // xsはIPAddress[]、つまりIEnumerableとIObservableを区別なくバラしているという狂気の融合!
.Subscribe(Console.WriteLine);
ContinueWithは、まあごく普通に結果が流れてきてるんだなー、程度。しかしRxのほうはヤバい。この場合のContinueWithに該当するのはSubscribeで、まあそれは普通なのですが、それよりしかし流れてくるIPAddress[]の[]がウザいので、Linq的に扱うならフラットにしたいよね。というわけで、IObservable<IPAddress[]>をSelectManyでIObservable<IPAddress>に変換しています。SelectManyはIObservableだろうとIEnumerableだろうと、平等にバラします。これは実にヤバい。狂気すら感じるパワー。皆も是非Rxを使ってこのヤバさを知って欲しい。
実行・待機
同時実行して、その結果を一辺に受けたい場合ってありますよね。そんな場合はForkJoinで。ForkJoinよく出てくるなあ。
string[] urls = new[] { "www.microsoft.com", "www.msdn.com" };
Task<IPAddress[]>[] tasks = new Task<IPAddress[]>[urls.Length];
for (int i = 0; i < urls.Length; i++)
{
tasks[i] = Task<IPAddress[]>.Factory.FromAsync(
Dns.BeginGetHostAddresses,
Dns.EndGetHostAddresses,
urls[i], null);
}
Task.WaitAll(tasks);
Console.WriteLine(
"microsoft.com resolves to {0} IP addresses. msdn.com resolves to {1}",
tasks[0].Result.Length,
tasks[1].Result.Length);
// WaitAll? ああ、ForkJoinで並行実行のことですか
Observable.ForkJoin(urls.Select(url =>
Observable.FromAsyncPattern<string, IPAddress[]>(Dns.BeginGetHostAddresses, Dns.EndGetHostAddresses)(url)))
.Run(xs => Console.WriteLine(
"microsoft.com resolves to {0} IP addresses. msdn.com resolves to {1}",
xs[0].Length, xs[1].Length));
そろそろマンネリ気味で疲れてきた。あ、最後にデッカイのがありますね。TaskではContinueWhenAllが初お目見え。でもRxでは別に変わらずForkJoinなんだよねえ。
// Task要の定義
static Task<string> DownloadStringAsTask(Uri address)
{
TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
WebClient client = new WebClient();
client.DownloadStringCompleted += (sender, args) =>
{
if (args.Error != null) tcs.SetException(args.Error);
else if (args.Cancelled) tcs.SetCanceled();
else tcs.SetResult(args.Result);
};
client.DownloadStringAsync(address);
return tcs.Task;
}
// Rx用の定義
public static IObservable<IEvent<DownloadStringCompletedEventArgs>> DownloadStringAsObservable(Uri address)
{
var client = new WebClient();
var con = Observable.FromEvent<DownloadStringCompletedEventHandler, DownloadStringCompletedEventArgs>(
h => new System.Net.DownloadStringCompletedEventHandler(h),
h => client.DownloadStringCompleted += h,
h => client.DownloadStringCompleted -= h)
.Take(1).Prune();
con.Connect();
client.DownloadStringAsync(address);
return con;
}
static int CountParagraphs(string s)
{
return Regex.Matches(s, "<p>").Count;
}
static void Main(string[] args)
{
Task<string> page1Task = DownloadStringAsTask(new Uri("http://www.microsoft.com"));
Task<string> page2Task = DownloadStringAsTask(new Uri("http://www.msdn.com"));
Task<int> count1Task = page1Task.ContinueWith(t => CountParagraphs(t.Result));
Task<int> count2Task = page2Task.ContinueWith(t => CountParagraphs(t.Result));
/// 全てが完了したら、Actionを実行
Task.Factory.ContinueWhenAll(new[] { count1Task, count2Task },
tasks =>
{
// tasks引数使わないのね(笑)
Console.WriteLine("<P> tags on microsoft.com: {0}", count1Task.Result);
Console.WriteLine("<P> tags on msdn.com: {0}", count2Task.Result);
});
// Rxではこうなる
Observable.ForkJoin(
DownloadStringAsObservable(new Uri("http://www.microsoft.com")),
DownloadStringAsObservable(new Uri("http://www.msdn.com")))
.Select(xs => xs.Select(e => CountParagraphs(e.EventArgs.Result)).ToArray())
.Subscribe(xs =>
{
Console.WriteLine("<P> tags on microsoft.com: {0}", xs[0]);
Console.WriteLine("<P> tags on msdn.com: {0}", xs[1]);
});
Console.ReadKey(); // 非同期実行なので終了しないように
}
ふむ(何がふむだ)
非同期とUIスレッド
Dispatcher.BeginInvokeさようなら!ですね。ObserveOnはRxだけの専売特許じゃない。Taskにだってあるもん。
void Button_Click(object sender, RoutedEventArgs e)
{
// ContinueWithの引数にTaskSchedulerを入れると非同期実行結果からUIを触れる
TaskScheduler uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
DownloadStringAsTask(new Uri("http://www.microsoft.com"))
.ContinueWith(t => { textBox1.Text = t.Result; }, uiTaskScheduler);
// Rxではすっかりお馴染みなObserveOnで(WPFの場合は省略形としてObserveOnDispatcherがある)
DownloadStringAsObservable(new Uri("http://www.microsoft.com"))
.ObserveOnDispatcher()
.Subscribe(ev => { textBox1.Text = ev.EventArgs.Result; });
}
真面目に、このObserveOnDispatcherは死ぬほど役立ちです。
まとめ
機能的には十分被る。そして、より汎用的なはずのRxが専用のはずのTaskでの処理を十分に代替出来てしまうという事実に驚く。Rx始まりすぎてる。今更言うのもアレですが、これは確実にヤバい。ビッグウェーブはもう既に来てる。乗り遅れてもいいんで乗ろう!
それにしてもMSDNマガジン素晴らしいなあ。この記事は実に入門向けに満遍なくも濃密で、素敵な時間を過ごせた。いやあ、一時期は機械翻訳のみになってたけれど、再び翻訳に戻って大変ありがたい。そして、そろそろRxもMSDNマガジンに記事が来てもいいのでは?もしくはMSKKの人がですね。どうでしょう。どうなんでしょう。どうなってるんでしょう。
あ、そういえば私は10月期のMicrosoft MVPに応募してたんですがそれはやっぱりダメだったよ。言うことを聞かないからね。これを見てるそこの君、以下略。来年の1月にretryしようかなー。それまでには記事増量、ってやること変わってないんじゃ結果は同じじゃないのというのががが。
Reactive Extensionsの非同期周りの解説と自前実装
- 2010-09-28
最近id:okazukiさんが凄い勢いでRx解説を書いていて凄い!そして、刺激を受けますねー。というわけで、今回はRxの非同期周りの解説をします。今日の昼に非同期処理を行うメソッドの戻り値は全てIObservable<T>にしてしまえばいいんじゃないんだろうかを読んで、Twitterで少しAsyncSubjectについて書いたのでそれをまとめて……、と思ったらReactive Extensions入門 11「非同期処理用のSubject」ですって!早!私なんて一年かけて何も書いていやしなかったりするのに、この早さは本当に見習いたい、ごほごほ。
そんなわけかで、色々と被ってしまっているのですが、Rxの非同期実行についてまとめます。まず、Rxの非同期の起点はStart, ToAsync, FromAsyncPatternの3つ。Start,ToAsyncはFromAsyncPatternの簡易化みたいなものなので、実質は1つです。これらを使うと長さ1のIObservable<T>として非同期処理が扱えるわけです。ところで非同期処理が完了するまでの間に複数Subscribeしたらどうなるのだろう、または非同期処理が完了してしまった後にSubscribeしたらどうなるのだろう? Silverlightで実際に触って試してみてください。
ボタンだらけでイミフ? 解説します……。Rxの非同期系処理は全てAsyncSubjectを通ります。AsyncSubjectのOnNextが非同期で実行された値の戻り値を、OnCompletedが非同期処理の完了を示します。通常はOnNextとOnCompletedはワンセットで非同期処理です。というわけで、例えばSubscribeを二回押す→OnNextを一回押す→OnCompletedを一回押すで、非同期のIObservable<int>に対して二つSubscribe(Console.WriteLine)したということになり、1が二つ右側のログに表示されたはずです。続けてSubscribeを押す(非同期処理が完了した後にSubscribeした場合)とどうなるか?というと、1が追加されたはずです。
以前にHot vs ColdとしてIObservableの性質を少し取り上げました。ColdはSubscribeするとすぐに実行される。HotはSubscribeしても値が流れてくるまで待機する。非同期におけるIObservable<T> = AsyncSubjectは、つまり、両方の性質を持ちます。OnCompleted以前はHotで、値が流れてくる(非同期処理が完了する)まで待機される。OnCompleted以後はColdとなって、Subscribeするとすぐに値を流す。
何となくAsyncSubjectの中身が想像付いてきました?そう、非同期の実行結果である値をキャッシュしています。もし複数回OnNextされたらどうなるか、というと、これは最後の値だけが残ります。(一度Resetしてから)OnNextを連打してからOnCompletedを押して、Subscribeしてみてください。
非同期というとキャンセルはどうするの?というと、ありますよ!Subscribeの戻り値はIDisposable。イベントのRx化、FromEventの場合はイベントのデタッチでしたが、非同期で使う場合はキャンセルになります。例えばSubscribeを押してから、Disposeを押して(キャンセル!)、OnNext→OnCompleted(非同期処理完了)を押してみてください。ログに何も出てきません。非同期処理が完了する前にDisposeしたということで、Subscribeがキャンセルされた、ということです。では、続けて(OnCompleted完了後)Subscribeを押すと……、ログに値が表示されます。Disposeは処理自体をキャンセルするわけではなく、 Subscribeのキャンセルということですね。
そうそう、ResetボタンはSubscribeやDispose、AsyncSubjectの状態をリセットして初期化します。Clearボタンはログの消去になります。
AsyncSubjectの簡易実装
AsyncSubjectの実態が大体分かったので、次は自分で実装してみましょう!その前にSubjectって何ぞや、というと、IObservable<T>かつIObserver<T>。これはRxネイティブのイベントだと思ってください。詳しくはReactive Extensions入門 + メソッド早見解説表をどうぞ。
とりあえず実装したコードを。
public class MyAsyncSubject<T> : IObservable<T>, IObserver<T>
{
bool isCompleted = false;
T lastValue = default(T);
readonly List<IObserver<T>> observers = new List<IObserver<T>>();
// OnCompleted済みなら即座にOnNext呼び出し、そうでないならListへAdd
public IDisposable Subscribe(IObserver<T> observer)
{
if (isCompleted)
{
observer.OnNext(lastValue);
observer.OnCompleted();
return Disposable.Empty;
}
else
{
observers.Add(observer);
// 正しくはキャンセルが可能なように、Disposeが呼ばれた際にListからremoveされるよう
// ラップした特別なIDisposableを返す必要があるけれど、簡略化した例ということでその辺は省きます
return Disposable.Empty;
}
}
// 初回呼び出しの場合は最後の値で全てのobserverのOnNextとOnCompletedを呼ぶ
public void OnCompleted()
{
if (isCompleted) return;
isCompleted = true;
observers.ForEach(o =>
{
o.OnNext(lastValue);
o.OnCompleted();
});
observers.Clear();
}
// OnCompletedと同じ。これも呼ばれたらCompleted済みとなる
public void OnError(Exception error)
{
if (isCompleted) return;
isCompleted = true;
observers.ForEach(o =>
{
o.OnError(error);
});
observers.Clear();
}
// Completed済みでなければキャッシュを置き換える
public void OnNext(T value)
{
if (isCompleted) return;
lastValue = value;
}
}
あくまでこれは簡易化したものです。実際はこれより、もう少し複雑です。あとlockを省いているのにも注意。まあ、ちゃんとしたのが知りたい場合はリフレクタとキャッキャウフフしてくださいということで。コードの中身ですが、Silverlightのデモで見てきた通りの、比較的単純な作りになっています。OnCompleted前後のSubscribeの挙動なんて、コードで見たほうが一目瞭然で早いですね。
これの挙動がRxの非同期系の挙動です。もしAsyncSubjectではなくSubject(値のキャッシュなし)を使った場合は、非同期開始からSubscribeまでの間に完了してしまった場合、何も起きないことになってしまいます。キャッシュを取るというのは、非同期に最適な理に叶った、というか、少なくとも不都合は起きないような挙動になります。もし自前で非同期でIObservable<T>を返すようなメソッドを実装する場合は、必ずAsyncSubjectを使いましょう。ユーザーの期待する挙動を取らなければならないという、義務として。
FromAsyncPatternの簡易実装
ここまで来たら、ついでなのでFromAsyncPatternも実装してしまいましょう!AsyncSubjectがあれば簡単です。あ、こちらでも断っておくと、あくまで簡易実装であって実際のものとは若干異なります。
static class Program
{
static Func<T, IObservable<TR>> MyFromAsyncPattern<T, TR>(Func<T, AsyncCallback, object, IAsyncResult> begin, Func<IAsyncResult, TR> end)
{
return arg =>
{
var asyncSubject = new AsyncSubject<TR>(Scheduler.ThreadPool); // おや、引数に……
// 引数を渡されたら、Subscribeを待たずBeginInvokeで非同期実行が始まります
begin.Invoke(arg, ar =>
{
TR result;
try
{
result = end.Invoke(ar); // EndInvokeで結果を得て
}
catch (Exception error)
{
asyncSubject.OnError(error);
return;
}
asyncSubject.OnNext(result); // OnNext!
asyncSubject.OnCompleted();
}, null);
return asyncSubject.AsObservable(); // SubjectのOnNextなどを隠す(なので昔はHideというメソッド名でした)
};
}
// ToAsyncはFunc/ActionのBeginInvoke,EndInvoke簡略化版です
// というのは少し嘘です、実際はSchedulerを使うのであってBeginInvokeは使いません、詳しくはまたそのうち
static Func<T, IObservable<TR>> MyToAsync<T, TR>(this Func<T, TR> func)
{
return MyFromAsyncPattern<T, TR>(func.BeginInvoke, func.EndInvoke);
}
// StartはToAsyncの引数無しのものを即時実行というものです
// Func<T>のみのFromAsyncPatternを作っていないので、Rx本来のToAsyncで書きます
static IObservable<T> MyStart<T>(Func<T> func)
{
return Observable.ToAsync(func).Invoke(); // ここで即座にInvoke
}
static void Main(string[] args)
{
// *が引数の分だけ並ぶという関数があったとする
Func<int, string> repeat = i => new String('*', i);
var obs = MyFromAsyncPattern<int, string>(repeat.BeginInvoke, repeat.EndInvoke)
.Invoke(3); // Subscribe時ではなく、Invokeした瞬間に非同期の実行は開始されていることに注意
obs.Subscribe(Console.WriteLine); // ***
Console.ReadKey();
}
}
FromAsyncPatternは引数の多さや引数のイミフさにビビりますが、デリゲートのBeginInvoke, EndInvokeに合わせてやるだけです。こんなの私もソラでは書けませんよー。さて、今回は引数を一つ取るFromAsyncPatternを定義してみました。戻り値がFunc<T, IObservable<TR>>なのは、引数を一つ取ってIObservable<T>にする、ということです。
中身は割と簡単で、とりあえずBeginInvokeして、EndInvokeを待たずに(そりゃ非同期なので当然だ)とりあえずAsyncSubjectを戻します。つまり、これでIObservableとしてはHotの状態。そして何らかの処理が終えたらEndInvokeで戻り値を得て、AsyncSubjectのOnNext, OnCompletedを呼んでやる。これでSubscribeされていたものが実行されて、ついでにIObservableとしてColdになる。これだけ。意外とシンプル。
ついでのついでなのでObservable.ToAsyncとObservable.Startも定義してみました。ToAsyncはFunc/Actionを簡単にRx化するもの。FromAsyncPatternだとジェネリックの引数を書いたりと、何かと面倒くさいですから。StartはToAsyncを更に簡略化したもので、引数なしのものならInvoke不要で即時実行するというもの。
実行開始タイミングについて
FromAsyncPatternは(ToAsync/Startも)Invokeした瞬間に非同期実行が始まります。 FromAsyncPattern().Invoke().Subscribe() といったように、直接繋げる場合は気にする必要も特にないかもですが、一時変数などに置いたりする場合などは、Subscribeされたときに初めて非同期実行が開始されて欲しい、と思ったりあるかもですね。そんな場合はDeferを使います。
// 100を返すだけのどうでもいい関数
Func<int> func = () =>
{
Console.WriteLine("fire");
return 100;
};
var start = Observable.Start(func);
var defer = Observable.Defer(() => Observable.Start(func));
var prune= Observable.Defer(() => Observable.Start(func)).Prune();
StartButton.Click += (sender, e) =>
{
start.Subscribe(Console.WriteLine);
};
DeferButton.Click += (sender, e) =>
{
defer.Subscribe(Console.WriteLine);
};
var isConnected = false;
ReplayButton.Click += (sender, e) =>
{
if (!isConnected) { prune.Connect(); isConnected = true; }
prune.Subscribe(Console.WriteLine);
};
何もボタンを押さなくてもログにfireと出てしまっています。Observable.Startのせいなわけですががが。そんなわけで、Deferを使うとSubscribeまで実行を遅延することができます。ところで注意なのが、どちらもIObservable<T>なのですが、StartはColdとしてキャッシュされた値を返し続けるのに対して、Deferは中の関数を再度実行します。もしキャッシュしたい場合は、Pruneを使うといいでしょう。Pruneはこのブログでも延々と出してきた値の分配のためのPublishの親戚で、Connect後は最後の値をキャッシュして返し続けるという、AsyncSubjectと同じ動作をします(というか中身がAsyncSubjectなのですが)。この辺の使い分けというのもヤヤコシイところですねえ、そもそもPruneっていうメソッド名がイミフ……。
まとめ
Linq to Objectsで、最初分からなかったんですよ、yield returnなどで返される遅延評価としてのIEnumerable<T>と、配列(これもIEnumerable<T>ではある)の違いが。初めてLinqを知った後、半年ぐらいは分からないままだった(C#歴も半年でしたが)。同じインターフェイスなのに、状態としては、ちょっと違う。こういうのって結構分かりづらくて、躓いてしまうところです。
Rxの厄介なところは、IObservable<T>が一つで色々な状態を持ちすぎ。HotとColdの違いもある上に、更には混じり合った状態まである、Deferと非Deferも、外からだけだと全く区別がつかない。もう分かりづらいったらない。これに関しては一個一個丁寧に見ていくしかない、かな。今回はRxにおける非同期を徹底的に解剖してみました。一つ一つ、丁寧に。Rxも徐々に盛り上がりつつあるようなので、これからも、私なりに出来る限りに情報を発信していけたらと思います。
ところで、凄くシンプルなんですよね、Rxって。何を言ってるんだ?って話ですが、ええと、ほら、あくまでも「簡易」だからってのもありますが、実装のコード行数も少ないし全然難しいことやってないという。Linq to Objectsもそれ自体は凄くシンプルで、シンプルなのにとんでもなく強力という。それと同じで。Rx触ってて、内部をリフレクタでちょろちょろと見てて、本当に驚く。シンプルなのだけど、自分じゃ絶対書けないし思いつけないしっていう。悔しいし、そして、憧れるところですねえ。
特に魔法もなく、素直に中間層厚めな実装は、パフォーマンスは(もがもがもがもが)。何か言いました?
ReactiveOAuth - Windows Phone 7対応のOAuthライブラリ
- 2010-09-12
Windows Phone 7用のOAuth認証ライブラリを作成し、公開しました。他のライブラリに比べての特徴は、非同期APIしか用意されていないWindows Phone 7での利用を念頭に置き、Reactive Extensions(Rx)をフル活用しているという点です。そもそもWindows Phone 7対応のOAuthライブラリが少ないので、特にWindows Phone 7開発者は是非どうぞ。
Windows Phone 7専用というわけでもないですが(Console/WPFで使えるよう、DLLとサンプルコードを用意してあります)、Windows Phone 7以外では別途Rxのインストールが必要です。Windows Phone 7環境では最初から入っているのでRxのインストール不要。Silverlight用は、コードコピペで別プロジェクト立てるだけで動くと思うんですが、確認取るのが面倒だった(クロスドメインがー)ので、そのうちに。
ところでそもそもRxって何、という人は Reactive Extensions入門 + メソッド早見解説表 をどうぞ。
何故Rxを使うのか
ReactiveOAuthの説明に入る前に、何故Rxを使うのかということを少し。理由は簡単で、非同期プログラミングは大変だから。論より証拠で、WebRequestのPOSTを全てBegin-Endパターンで構築してみましょう。
var req = (HttpWebRequest)WebRequest.Create("http://google.co.jp/"); // dummy
req.Method = "POST";
req.BeginGetRequestStream(ar =>
{
var stream = req.EndGetRequestStream(ar);
stream.BeginWrite(new byte[10], 0, 10, _ar =>
{
stream.EndWrite(_ar);
req.BeginGetResponse(__ar =>
{
var res = req.EndGetResponse(__ar);
var resStream = res.GetResponseStream();
var s = new StreamReader(resStream).ReadToEnd();
Console.WriteLine(s);
}, null);
}, null);
}, null);
コールバックの連鎖とはこういうことであり、大変酷い。冗談のようだ。こんなに面倒なら、こんなに苦しいのなら、非同期などいらぬ!しかし現実問題、Silverlightには、Windows Phone 7には、非同期APIしか搭載されていません。Begin-Endが強要される世界。理由は分かる。ユーザーエクスペリエンスの為でしょう。モバイル機器は性能が貧弱だから重い処理はいけない、と言うけれど、大事なのは処理が重いか否かではなく、体感。UIを止めさえしなければ、不快感を与えることはない。だから、強制的に非同期操作のみとした。
けれど、それで開発難しくなったり面倒になってしまってはいけない。非同期処理が簡単に出来れば……。その答えが、Rx。「簡単に出来るから」「開発者が幸せで」「全てが非同期になり」「ユーザーも幸せになる」。楽しい開発って大事だよね。本当にそう思っていて。開発者が不幸せで、コードに愛がなければ良いものなんて生まれやしないんだって、本当に思っていて。
// Rxならこう書ける(...AsObservableは拡張メソッドとして別途定義)
req.GetRequestStreamAsObservable()
.SelectMany(stream => stream.WriteAsObservable(new byte[10], 0, 10))
.SelectMany(_ => req.GetResponseAsObservable())
.Select(res => new StreamReader(res.GetResponseStream()).ReadToEnd())
.Subscribe(Console.WriteLine);
// SelectManyが苦手ならばクエリ構文という手もあります
var query = from stream in req.GetRequestStreamAsObservable()
from _ in stream.WriteAsObservable(new byte[10], 0, 10)
from res in req.GetResponseAsObservable()
select new StreamReader(res.GetResponseStream()).ReadToEnd();
query.Subscribe(Console.WriteLine);
ネストが消滅して、メソッドチェーンの形をとって非同期が同期的のように書けるようになります。利点は他にもあって、様々な操作(合成・射影・抽出・待機などなど)が可能になる、ということもありますが、それはまたそのうち。
ところで、最初のコールバックの連鎖って何だか見覚えのあるような雰囲気ありませんか?JavaScriptで。そう、XmlHttpRequestであったりsetTimeoutであったりの連鎖と同じです。RxのJavaScript版、RxJSではJavaScriptでのそれらのネストを殺害することが可能です。興味があれば、そちらも是非試してみてくださいな。
ReactiveOAuthとは?
OAuthであっても、ようするにWebRequestなわけです。GET/POSTしてResponseを取ってくるということにかわりはない。そんなわけで、ネットワーク通信してResponseを取ってくる部分を片っ端から全てIObservableにしたのがReactiveOAuthです。Rxにべったり依存したことで、良くも悪くも、他のOAuthライブラリとは全く毛色の違う仕上がりになっています。
とはいっても、とにかく「簡単に書けること」にこだわりを持ってデザインしたので、利用自体は簡単ですし、Rxの知識もそんなに必要ありません。普通に取得するなら、SelectとSubscribeしか使わないので、全然安心です!まあ、できれば、これが入り口になってRxの世界を知ってもらえると嬉しいなあ、という皮算用もあったりですが。
使いかた1. AccessToken取得
デスクトップアプリケーション上でのOAuthの仕組みを簡単に説明すると、RequestToken取得→認証URL表示→PINコード入力→PINコード+RequestTokenを使ってAccessToken取得。という形になっています。まず、ユーザー名・パスワードの代わりとなるものはAccessTokenです。これを取得して、API呼び出しの時に使い、また、パスワード代わりに保存するわけです。そのRequestTokenやPINコードはAccessTokenを取得するための一時的な認証用キーということです。
AccessToken取得までにはOAuthAuthorizerクラスを使います。
// グローバル変数ということで。
const string ConsumerKey = "consumerkey";
const string ConsumerSecret = "consumersecret";
RequestToken requestToken;
AccessToken accessToken;
private void GetRequestTokenButton_Click(object sender, RoutedEventArgs e)
{
var authorizer = new OAuthAuthorizer(ConsumerKey, ConsumerSecret);
authorizer.GetRequestToken("http://twitter.com/oauth/request_token")
.Select(res => res.Token)
.ObserveOnDispatcher()
.Subscribe(token =>
{
requestToken = token;
var url = authorizer.BuildAuthorizeUrl("http://twitter.com/oauth/authorize", token);
webBrowser1.Navigate(new Uri(url)); // navigate browser
});
}
GetRequestTokenとBuildAuthorizeUrlを使い、RequestTokenの取得と、内蔵ブラウザに認証用URLを表示させました。
private void GetAccessTokenButton_Click(object sender, RoutedEventArgs e)
{
var pincode = PinCodeTextBox.Text; // ユーザーの入力したピンコード
var authorizer = new OAuthAuthorizer(ConsumerKey, ConsumerSecret);
authorizer.GetAccessToken("http://twitter.com/oauth/access_token", requestToken, pincode)
.ObserveOnDispatcher()
.Subscribe(res =>
{
// Token取得時のレスポンスには、Token以外に幾つかのデータが含まれています
// Twitterの場合はuser_idとscreeen_nameがついてきます
// ILookup<string,string>なので、First()で取り出してください
UserIdTextBlock.Text = res.ExtraData["user_id"].First();
ScreenNameTextBlock.Text = res.ExtraData["screen_name"].First();
accessToken = res.Token; // AccessToken
});
}
画像は認証が全て終わった時の図になっています。RequestTokenとPinCodeをGetAccessTokenメソッドに渡すだけです。これでAccessTokenが取得できたので、全ての認証が必要なAPIにアクセス出来るようになりました。
使いかた2. APIへのGet/Post
ここからはConsoleApplicationのサンプルコードで説明。
var client = new OAuthClient(ConsumerKey, ConsumerSecret, accessToken)
{
Url = "http://api.twitter.com/1/statuses/home_timeline.xml",
Parameters = { { "count", 20 }, { "page", 1 } },
ApplyBeforeRequest = req => { req.Timeout = 1000; req.UserAgent = "ReactiveOAuth"; }
};
client.GetResponseText()
.Select(s => XElement.Parse(s))
.Run(x => Console.WriteLine(x.ToString()));
IObservable<T>連鎖の最後のメソッドとしてRunを使うと、同期的になります。通常はSubscribeで非同期にすると良いですが、コンソールアプリケーションなどでは、同期的な動作のほうが都合が良いでしょう。
OAuthClientを作成し、オブジェクト初期化子でURL、パラメータ(コレクション初期化子が使えます)を設定したら、GetResponseTextを呼ぶだけ。あとはIObservable<string>になっているので、Linqと同じように操作していけます。
ApplyBeforeRequestではリクエストが発行される前に、生のHttpWebRequestが渡されるので(!)、TimeoutやUserAgentなど細かい設定がしたい場合は、ここにラムダ式を埋めてください。
では、POSTは?
new OAuthClient(ConsumerKey, ConsumerSecret, accessToken)
{
MethodType = MethodType.Post,
Url = "http://api.twitter.com/1/statuses/update.xml",
Parameters = { { "status", "PostTest from ReactiveOAuth" } }
}.GetResponseText()
.Select(s => XElement.Parse(s))
.Run(x => Console.WriteLine("Post Success:" + x.Element("text")));
POSTの場合はMethodTypeにMethodType.Postを指定します(デフォルトがGETなので、GETの場合は指定の省略が可)。それ以外はGETと同じです。Urlとパラメータ指定して、GetResponse。
ストリーミングもいけます
OAuthClientには3つのメソッドがあります。GetResponseは生のWebResponseを返すもので細かい制御をしたい時にどうぞ。GetResponseTextはStreamReaderのReadToEndで応答をテキストに変えたものを返してくれるもので、お手軽です。そのままXElement.Parseとかに流すと楽ちん。そして、GetResponseLinesはReadLineで一行ずつ返してくれるもの、となっています。GetResponseTextとGetResponseLinesは型で見ると両方共IObservable<string>なため戸惑ってしまうかもですが、前者は流れてくるのは一つだけ、後者は行数分だけ、となります。
GetResponseLinesはStreamingAPIで使うことを想定しています。とりあえず、WPF用のサンプルを見てください。
var client = new OAuthClient(ConsumerKey, ConsumerSecret, accessToken)
{
Url = "http://chirpstream.twitter.com/2b/user.json"
};
// streamingHandleはIDisposableで、これのDisposeを呼べばストリーミング停止
streamingHandle = client.GetResponseLines()
.Where(s => !string.IsNullOrWhiteSpace(s)) // filter invalid data
.Select(s => DynamicJson.Parse(s))
.Where(d => d.text()) // has text is status
.ObserveOnDispatcher()
.Subscribe(
d => StreamingViewListBox.Items.Add(d.user.screen_name + ":" + d.text),
ex => MessageBox.Show(ReadWebException(ex))); // エラー処理
ストリーミングAPIを使うにあたっても、何の面倒くささもなく、至って自然に扱えてしまいます!Dynamicを使ったJsonへの変換にはDynamicJson - C# 4.0のdynamicでスムーズにJSONを扱うライブラリを、また、RxとStreamingAPIとの相性については過去記事C#とLinq to JsonとTwitterのChirpUserStreamsとReactive Extensionsを見てください。
そうそう、それとネットワーク通信で起こったエラーのハンドリングが、SubscribeのOnErrorに書くだけで済むというのもRxを使って嬉しいことの一つです。
実装について
構造は全体的に@ugaya40さんのOAuthAccessがベースになっています(パク……)。そもそもにTwitterTL to HTMLのOAuth対応した時に、OAuthAccessを使って、あー、OAuth周りってRxに乗せると快適になるなー、とか思ったのが作ろうとした最初の動機だったりもして。非常に感謝。
WebClient風の、認証がOAuthなだけのベタなWebRequestラッパーという感じなので、特別なところはありません。インターフェイスとかなくて、本当にただのベタ書き。特に奇をてらってるところはないんですが、ストリーミングAPIで使うために用意したGetResponseLinesは個人的には笑えたり。
var req = WebRequest.Create(Url);
return Observable.Defer(() => req.GetRequestStreamAsObservable())
.SelectMany(stream => stream.WriteAsObservable(postData, 0, postData.Length))
.SelectMany(_ => req.GetResponseAsObservable());
.Select(res => res.GetResponseStream())
.SelectMany(s => Observable.Using(() => new StreamReader(s), sr => Observable.Repeat(sr)))
.TakeWhile(sr => !sr.EndOfStream)
.Select(sr => sr.ReadLine());
うわー……。利用者としては、ただのIObservable<string>としか見えないので、前段階でこんなにチェーンが繋がってるだなんてこと、気にする必要は全くないんですけどねー。これがベストな書き方だとは全然思えないので、誰かアドバイス欲すぃです。
まとめ
RxというとLinq to Events、イベントのLinq化という方向が目につきますが、今回は非同期のLinq化のほうにフォーカスしました。何というか、実に、素晴らしい!asynchronus programming is hard、と、思っていた時もありました。今や私達にはRxがある。恐れることはなにもない。
今回WPFとWindows Phone 7(Silverlight)でサンプルを作ったのですが、コードがコピペで、完全な互換性もって動いちゃうんですね。WPFで書いてSilverlightに持っていく時に、ああ、BeginGetResponseに書き換えなきゃ…… みたいなことが起こらない。最初から非同期で統一することで、全部ライブラリがネットワーク周りを吸収してくれる。非同期→同期にするのも簡単だし(RunやToEnumerableを使えばいい)、そもそも、Rxの土台に乗っている方が、普通に同期的に書くよりもむしろ楽だったりします。
個人的には、Windows Phone 7のローンチに粗製乱造Twitterアプリを送り込むという野望があるんですがねえー。いや、粗製乱造にするつもりはないんですが、機能をザックリと削って、一般性を無視して「私が使うシチュエーションで私が使いやすいような」アプリを出したいなーと思ってます。構想はあって、そこそこ尖った個性ある内容になる予定なので一部の人がフィットしてくれればいいな、と。多くの人に目に触れては欲しいのでローンチのタイミングは外したくない。問題はWorldのローンチタイミングに合わせてもJapanだと実機がないってことですね!開発機欲しい(お金は払いますからどうかー)。
と、思ってたのですがサンプル作りで、あきらかーなXAML知識のなさ(ていうか何も知りません)が露呈したので、ローンチに間にあわせるとか寝言すぎるのですが。せめてJapanのローンチまでにはそれなりな技量を身につけたいところです。
C#から使うMicrosoft Ajax MinifierでのJavaScriptコード圧縮
- 2010-09-09
いつのまにか独立してCodePlex入りしているMicrosoft Ajax Minifier。その名の通り、Microsoft謹製のJavaScript/CSSの圧縮/整形ツールです。発表からすぐに、GoogleのClosure Compilerが出てしまったのですっかり影も薄く、ていうか名前悪いよね、Ajax関係ないじゃん……。CodePlexのプロジェクトページも何だか活気ない寂れた感じで、あーあ、といった趣。良いツールなんですけどねえ。
コマンドライン版とDLL版が用意されていて、コマンドラインの、単体で実行可能なexeの解説は【ハウツー】Microsoft Ajax MinifierでJavaScriptを縮小化しようで解説されているので、DLL版の使い方を簡単に解説します。
C#が使える人ならばDLL版のほうが遥かに使いやすかったりして。設定をダラダラと引数を連ねるのではなく、オブジェクト初期化子とenumで出来るので、そう、IntelliSenseが効くわけです。ヘルプ要らずで書けるのは快適。Visual Studioは偉大だなぁ。コマンドライン, PowerShellを捨て、VSのConsoleApplicationを常に立ち上げよう。実際、ちょっとしたテキスト処理とかC#で書いちゃうんですよねえ、私。Linqが楽だから。
Minifierオブジェクトを作ってMinifyJavaScript(CSSの場合はMinifyStyleSheet)メソッドを実行するだけなので、コード見たほうが早いかな。例としてlinq.jsをlinq.min.jsに圧縮します。そして、解析されたWarningをコンソールに表示します。ただの圧縮だけでなく、このコード分析機能が実にありがたい。
using System;
using System.IO;
using Microsoft.Ajax.Utilities;
class Program
{
static void Main(string[] args)
{
var minifier = new Minifier
{
WarningLevel = 3, // 最大4。4はウザいのが多いので3がいいね。
FileName = "linq.js" // エラー表示用に使うだけなので、なくてもいい
};
var settings = new CodeSettings // CSSの場合はCSSSettings
{
LocalRenaming = LocalRenaming.CrunchAll,
OutputMode = OutputMode.SingleLine, // MultiLineにすると整形
IndentSize = 4, // SingleLineの時は意味なし
CollapseToLiteral = true,
CombineDuplicateLiterals = true
// その他いろいろ(それぞれの意味はIntelliSenseのsummaryを読めば分かるね!)
};
var load = System.IO.File.ReadAllText(@"linq.js"); // 読み込み
// 最後の引数はグローバル領域に何の変数が定義されてるか指定するもの
// 別になくても構わないんだけど、warningに出るので多少は指定しておく
var result = minifier.MinifyJavaScript(load, settings, "Enumerable", "Enumerator", "JSON", "console", "$", "jQuery");
// Warningで発見されたエラーはErrorsに格納されてる
foreach (var item in minifier.Errors)
{
Console.WriteLine(item);
}
File.WriteAllText("linq.min.js", result); // 書き出し
Console.WriteLine();
Console.WriteLine("Original:" + new FileInfo("linq.js").Length / 1024 + "KB");
Console.WriteLine("Minified:" + new FileInfo("linq.min.js").Length / 1024 + "KB");
}
}
二つエラー出てますね。変数がvarで宣言されてないそうです。つまり、コードがその行通ると変数がグローバルに置かれてしまいます。確認したら、OrderBy実行したらsortContextという変数がグローバルに飛んでしまってました。ええ、つまりバグと言っていいです。……。あうあう、すみません、直します。これからは必ずAjax Minifierの分析を通してから公開しよう。どうでもよくどうでもよくないんですがlinq.jsもアップデートすべきことが結構溜まってるので近いうちには……。
というわけで、Ajax Minifierの偉大さが分かりました。素晴らしい!みんな使おう!DLLの形になっているので、T4 Templateと混ぜたりなどもしやすく、夢が膨らむ。minifyだけでなく、JSのパーサーとして抽象構文木を取り出したりなども出来ます。
WebでGUIでサクッと実行できる環境とかあるといいと思うんですよね。それこそDLLになっているので、Silverlightならすぐ作れるわけですし。誰か作ればいいのに。いや、お前が作れよって話なんですが。Ajax Minifierが出てすぐの頃に作ろうとしてお蔵入りしちゃったんだよね、ふーみぅ。そうそう、あと、VSと統合されて欲しい!このWarningは大変強力なので、その場でVSのエラー一覧に表示して欲しいぐらい。今のVSのエラー表示はWarningLevelで言うところの1ぐらいで警告出してくれて、それはそれで良い感じなんですが、やっぱこう、もっとビシッと言って欲しいわけですよ。
ワンクリックで整形/圧縮してくれるのとエラー一覧表示が出来るようなVisual Studio拡張を誰か作って欲しいなあ。いやー、本気で欲しいので、とりあえず私も自分で作ってみようと思い、VS2010 SDKは入れてみた。気力が折れてなければ来月ぐらいには公開、したい、です、予定は未定のやるやる詐欺ですががが。
TwitterTLtoHTML ver.0.2.0.0
- 2010-09-02
TwitterのBasic認証の有効期限が8月いっぱいだったのですよね。それは知っていて動かなくなるのも知っておきながら、実際に動かなくなるまで放置していたという酷い有様。結果的に自分で困ってました(普通に今も使っていたので)。というわけで、TwitterTL to HTMLをOAuth対応にしました。認証時のアプリケーション名がTL to HTMLなのですが、これは「Twitter」という単語名をアプリケーション名に入れられないためです。面倒くさいねえ。
OAuth認証は@ugaya40さんのOAuthAccessを利用しています。XboxInfoTwitでは自前のものを使っていたのですが、どうしょうもなく酷いので自分で作り直すかライブラリを使うか、でズルズル悩んで締切りを迎えたのでライブラリ利用に決定ー。使いやすくて良いと思います。
T4による不変オブジェクト生成のためのテンプレート
- 2010-08-30
不変欲しい!const欲しい!readonlyをローカル変数にもつけたい!という要望をたまに見かけるこの頃。もし、そういった再代入不可というマークがローカル変数に導入されるとしたら、readonlyの使い回しだけは勘弁です。何故って、ローカル変数なんて大抵は再代入しないので、readonly推奨ということになるでしょう、そのうちreadonly付けろreadonly付けろというreadonly厨が出てくるのは目に見えています。
良いことなら付ければいいじゃない、というのはもっともですが、Uglyですよ、視覚的に。readonly var hoge = 3 だなんて、見たくはない。頻繁に使うほうがオプションで醜く面倒くさいってのは、良くないことです。let hoge = 3 といったように、let、もしくはその他のキーワード(valとか?)を導入するならば、いいかな、とは思いますが。
それに、ただ単にマークしただけじゃあ不変を保証するわけでもない……。例えばListなんてClearしてAddRangeしたのと再代入とは、どう違うの?的な。難しいねえ。そんなimmutableの分類に関してはufcppさんのimmutableという記事が、コメント欄含め参考になりました。
そうはいっても、そんなにガチに捉えなくても、不変にしたいシチュエーションはいっぱいあります。実はオブジェクト指向ってしっくりきすぎるんです! 不変オブジェクトのすゝめ。 - Bug Catharsis。おお、すすめられたい。ところでしかし、こういう時にいつも疑問に思っているのは、生成どうすればいいのだろう、ということ。今のところ現実解としてあるのはreadonly、つまり、コンストラクタに渡すしかないのですが……
public Hoge(int a, int b, int c, string d, string e, DateTime f, .....
破綻してる。こんなクソ長いコンストラクタ見かけたら殺していいと思う。全くもって酷い。さて、どうしましょう。こういう場合はビルダーを使いましょう、とはEffective Javaが言ってますので(私、この本あんま好きじゃないんだよねー、とかはどうでもいいんですがー)とりあえずストレートに従ってみます。
// あまり行数使うのもアレなので短くしますが、実際は10行ぐらいあると思ってください
Hoge hoge = new HogeBuilder()
.Age(10)
.Name("hogehoge")
.Build();
まあ、悪くない、ですって?いえいえ、これはBuilder作るの面倒くさいし、第一Java臭い。メソッドチェーンだからモダンで素敵、と脳が直結してる人は考えが一歩足らない。むしろ古臭い。最近は流れるようなインターフェイスとかも割と懐疑的で、私は。頂くのはアイディアだけであって、書き方に関しては、各言語にきっちり馴染ませるべき。先頭の大文字小文字だけ整えて移植だとか、愚かな話。というわけで、C#ならオブジェクト初期化子を使おう。
var hoge = new HogeBuilder
{
Age = 10,
Name = "hogehoge"
}.Build();
// 暗黙的な型変換を使えばBuildメソッドも不要になる(私はvarのほうが好みですが)
Hoge hoge = new HogeBuilder
{
Age = 10,
Name = "hogehoge"
};
ええ、これなら悪くない。オブジェクト初期化子は大変素晴らしい(本当にそろそろModern C# Designをですね……)。ビルダーを作る手間もJava方式に比べ大幅に軽減されます(set専用の自動プロパティを用意するだけ)。それにIntelliSenseのサポートも効きます。
未代入のもののみリストアップしてくれる(Ctrl+Space押しだと全部出てきたりする、バグですかね、困った困った)。そういえばで、これは、不変である匿名型の記法とも似ています。余分なのは.Build()だけで、書く手間的にはそんな変わらない。
前説が長ったらしくなりました。本題は「匿名型のような楽な記法で不変型を生成したい」が目標です。C#の現在の記法では、それは無い。欲しいなあ。名前付き引数使えば似たような雰囲気になると言えばなるんですが、アレ使うと「省略可」な雰囲気が出てダメ。ビルダーで作りたいのは、原則「省略不可」なので。
なければ作ればいいじゃない、オブジェクト初期化子を使って.Buildで生成させるビルダーを作れば似たような感じになる。あとは、手動でそれ定義するの非常に面倒なので、そう、T4で自動生成しちゃえばいいぢゃない。
以下コード。例によってパブリックドメインで。別にブログにベタ貼りなコードは自明でいいんじゃないかって気もするんですが、宣言は一応しておいたほうがいいのかなー、と。
<#@ assembly Name="System.Core.dll" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ output extension="Generated.cs" #>
<#
// 設定:クラス名はそのまま文字列で入力
// クラスの持つ変数は、コンストラクタに書くみたいにdeffinesに
// "string hoge","int huga" といった形で並べてください
// usingとnamespaceは、直下の出力部を直に弄ってください
// partial classなので、これをベースにメソッドを足す場合は別ファイルにpartialで定義することを推奨します
// Code Contractsに関わる部分は(ContractVerification属性とContract.EndContractBlock())は、
// 対象がWindows Phone 7などContractが入っていない環境下では削除してください(通常の.NET 4環境では放置で大丈夫)
var className = "Person";
var deffines = new DeffineList {
"string name",
"DateTime birth",
"string address"
};
#>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
namespace Neue.Test
{
[DebuggerDisplay(@"<#= deffines.DebuggerDisplay #>", Type = "<#= className #>")]
public partial class <#= className #> : IEquatable<<#= className #>>
{
<# foreach(var x in deffines) {#>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly <#= x.TypeName #> <#= x.FieldName #>;
public <#= x.TypeName #> <#= x.PropName #> { get { return <#= x.FieldName #>; } }
<# } #>
private <#= className #>(<#= deffines.Constructor #>)
{
<# foreach(var x in deffines) {#>
this.<#= x.FieldName #> = <#= x.FieldName #>;
<# } #>
}
[ContractVerification(false)]
public static implicit operator Person(Builder builder)
{
return builder.Build();
}
public bool Equals(<#= className #> other)
{
if (other == null || GetType() != other.GetType()) return false;
if (ReferenceEquals(this, other)) return true;
return EqualityComparer<<#= deffines.First().TypeName #>>.Default.Equals(<#= deffines.First().FieldName #>, other.<#= deffines.First().FieldName #>)
<# foreach(var x in deffines.Skip(1)) {#>
&& EqualityComparer<<#= x.TypeName #>>.Default.Equals(<#= x.FieldName #>, other.<#= x.FieldName #>)
<# } #>
;
}
public override bool Equals(object obj)
{
var other = obj as <#= className #>;
return (other != null) ? Equals(other) : false;
}
public override int GetHashCode()
{
var hash = 0xf937b6f;
<# foreach(var x in deffines) {#>
hash = (-1521134295 * hash) + EqualityComparer<<#= x.TypeName #>>.Default.GetHashCode(<#= x.FieldName #>);
<# } #>
return hash;
}
public override string ToString()
{
return "{ " + "<#= deffines.First().PropName #> = " + <#= deffines.First().FieldName #> +
<# foreach(var x in deffines.Skip(1)) {#>
", <#= x.PropName #> = " + <#= x.FieldName #> +
<# } #>
" }";
}
public class Builder
{
<# foreach(var x in deffines) {#>
public <#= x.TypeName #> <#= x.PropName #> { private get; set; }
<# } #>
public <#= className #> Build()
{
<# foreach(var x in deffines) {#>
if ((object)<#= x.PropName #> == null) throw new ArgumentNullException("<#= x.PropName #>");
<# } #>
Contract.EndContractBlock();
return new <#= className #>(<#= string.Join(", ", deffines.Select(d => d.PropName)) #>);
}
}
}
}
<#+
class Deffine
{
public string TypeName, FieldName, PropName;
public Deffine(string constructorParam)
{
var split = constructorParam.Split(' ');
this.TypeName = split.First();
this.FieldName = Regex.Replace(split.Last(), "^(.)", m => m.Groups[1].Value.ToLower());
this.PropName = Regex.Replace(FieldName, "^(.)", m => m.Groups[1].Value.ToUpper());
}
}
class DeffineList : IEnumerable<Deffine>
{
private List<Deffine> list = new List<Deffine>();
public void Add(string constructorParam)
{
list.Add(new Deffine(constructorParam));
}
public string DebuggerDisplay
{
get
{
return "\\{ " + string.Join(", ", list.Select(d =>
string.Format("{0} = {{{1}}}", d.PropName, d.FieldName))) + " }";
}
}
public string Constructor
{
get { return string.Join(", ", list.Select(d => d.TypeName + " " + d.FieldName)); }
}
public IEnumerator<Deffine> GetEnumerator()
{
return list.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
#>
以下のようなのが出力されます(長いねー)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
namespace Neue.Test
{
[DebuggerDisplay(@"\{ Name = {name}, Birth = {birth}, Address = {address} }", Type = "Person")]
public partial class Person : IEquatable<Person>
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly string name;
public string Name { get { return name; } }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly DateTime birth;
public DateTime Birth { get { return birth; } }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly string address;
public string Address { get { return address; } }
private Person(string name, DateTime birth, string address)
{
this.name = name;
this.birth = birth;
this.address = address;
}
[ContractVerification(false)]
public static implicit operator Person(Builder builder)
{
return builder.Build();
}
public bool Equals(Person other)
{
if (other == null || GetType() != other.GetType()) return false;
if (ReferenceEquals(this, other)) return true;
return EqualityComparer<string>.Default.Equals(name, other.name)
&& EqualityComparer<DateTime>.Default.Equals(birth, other.birth)
&& EqualityComparer<string>.Default.Equals(address, other.address)
;
}
public override bool Equals(object obj)
{
var other = obj as Person;
return (other != null) ? Equals(other) : false;
}
public override int GetHashCode()
{
var hash = 0xf937b6f;
hash = (-1521134295 * hash) + EqualityComparer<string>.Default.GetHashCode(name);
hash = (-1521134295 * hash) + EqualityComparer<DateTime>.Default.GetHashCode(birth);
hash = (-1521134295 * hash) + EqualityComparer<string>.Default.GetHashCode(address);
return hash;
}
public override string ToString()
{
return "{ " + "Name = " + name +
", Birth = " + birth +
", Address = " + address +
" }";
}
public class Builder
{
public string Name { private get; set; }
public DateTime Birth { private get; set; }
public string Address { private get; set; }
public Person Build()
{
if ((object)Name == null) throw new ArgumentNullException("Name");
if ((object)Birth == null) throw new ArgumentNullException("Birth");
if ((object)Address == null) throw new ArgumentNullException("Address");
Contract.EndContractBlock();
return new Person(Name, Birth, Address);
}
}
}
}
これで、どれだけ引数の多いクラスであろうとも、簡単な記述でイミュータブルオブジェクトを生成させることが出来ます。しかも、普通にクラス作るよりも楽なぐらいです、.ttをコピペって、先頭の方に、コンストラクタに並べる型を書くだけ。後は全部自動生成任せ。もし積極的に使うなら、Generated.csのほうを消して.ttのみにした状態で、ファイル→テンプレートのエクスポートで項目のエクスポートをすると使い回しやすくて素敵と思われます、項目名はImmutableObjectとかで。
// 書くときはこんな風にやります
var person1 = new Person.Builder
{
Name = "hoge",
Birth = new DateTime(1999, 12, 12),
Address = "Tokyo"
}.Build();
// 暗黙的な型変換も実装されているので、.Buildメソッドの省略も可
Person person2 = new Person.Builder
{
Name = "hoge",
Birth = new DateTime(1999, 12, 12),
Address = "Tokyo"
};
// 参照ではなく、全てのフィールドの値の同値性で比較される
Console.WriteLine(person1.Equals(person2)); // true
匿名型の再現なので、EqualsやGetHashCodeもオーバーライドされて、フィールドの値で比較を行うようになっています。この辺はもう手動だと書いてられないですよね。ReSharperなどを入れて生成をお任せする、という手はありますが。
==はオーバーライドされていません。これもまた匿名型の再現なので……。Tupleもされてないですしね。これは、フィールドをreadonlyで統一しようと「変更可能」な可能性が含まれるので==は不適切、というガイドライン的なもの(と解釈しました)に従った結果です。変更可能云々は、下の方で解説します。
Code Contracts
更に、Code Contractsを入れれば、値の未代入に対するコンパイラからの静的チェックまで得られます!下記画像のは、Addressが未入力で、通常は実行時に例外が飛ぶことで検出するしかないですが、Code Contractsが静的チェックで実行前にnullだと警告してくれています。
ビルダーの欠点は未代入の検出が実行時まで出来なかったりすること。インターフェイスで細工することで、順番を規定したり、必ず代入しなければならないものを定義し終えるまでは.BuildメソッドがIntelliSenseに出てこないようにする。などが出来ますが、手間がかかりすぎて理想論に留まっている気がします。
簡単であることってのはとても大事で、過剰な手間暇や複雑な設計だったりってのは、必ず無理が生じます。手間がかかること、複雑であることは、それ自体が良くない性質の一つであり、メリットがよほど上回らない限りは机上の空論にすぎない。
今回、Code Contractsのパワーにより、シンプルなオブジェクト初期化子を使ったビルダーでも未代入の静的チェックをかませる、という素敵機構が実現しました。残念ながらCode Contractsは要求環境が厳しいです。アドインを入れてない/入れられない(Express)場合はどうなるのか、というと、.NET 4にクラス群は入っているので、コンパイル通らないということはありません。普通にArgumentNullExceptionがthrowされるという形になります。
私が考えるに、.NET 4でクラスが入ったのって、Code Contractsのインストールの有無に関係なくコードが共有出来るように、という配慮でしかない予感。ExpressでContractクラスを使う意味は、あまりなさそうですね。Windows Phone 7環境など、Contractクラスそのものがないような場合では、T4のBuilderクラスBuildメソッドのContract.EndContractBlock();の一行とimplict operatorのContractVerification属性を削除してください。自分で好きに簡単に書き換えられるのもT4の良さです。
今回はnullチェックしかしていないので、つまり値型の未代入には無効です。何とかして入れたいとは思ったんですが、例えば対策として値型をNullableにするにせよType情報が必要で、そのためにはAssembly参照が必要で、と設定への手間が増えてしまうので今回は止めました(このT4はただ文字列を展開しているだけで、完全にリフレクション未使用)
Code Contractsに関しては、事前条件のnullチェックにしか使っていなくて真価の1%も発揮されていないので、詳しくはとある契約の備忘目録。契約による設計(Design by Contract)で信頼性の高いソフトウェアを構築しよう。 - Bug Catharsisなどなどを。不変オブジェクトに関してもそうだけれど、zeclさんの記事は素晴らしいです。
Code Contracts自体は、メソッド本体の上の方で、コントラクトの記述が膨れ上がるのは好きでないかも。従来型の、if-then-throwでの引数チェックも、5行を超えるぐらいになるとウンザリしますね。ご丁寧に{で改行して、if-then-throwの一個のチェックに4行も使って、それが5個ぐらい連なって20行も使いやがったりするコードを見ると発狂します。そういう場合に限ってメソッド本体は一行で他のメソッド呼んでるだけで、更にその、他のメソッドの行頭にも大量の引数チェックがあったりすると、死ねと言いたくなる。コードは視覚的に、横領域の節約も少しは大事だけど、縦も大事なんだよ、分かってよね……。メソッド本体が1000行とか書く人じゃなく、100行超えたら罰金(キリッ とか言ってる人だけど、それならガード句が10行超えたら罰金だよこっちとしては。
話が脱線した。つまるところ、コントラクトはライブラリレベルで頑張るよりも、言語側でのサポートが必要な概念ですね、ということで。実際rewriterとか、ライブラリレベル超えて無茶しやがって、の領域に踏み込んでいますし<Code Contracts。
プラスアルファ
partial classで生成されるので(デフォルトではクラス名.Generated.cs)、別ファイルにクラスを作ることで、フィールドの増減などでT4を後で修正しても、影響を受けることなくメソッドを追加することができます。
// Person.csという形で別ファイルで追加
using System;
namespace Neue.Test
{
public partial class Person
{
public int GetAge(DateTime target)
{
return (target.Year - birth.Year);
}
}
}
それと、nullチェックだけじゃなくきっちりBuildに前提条件入れたい(もしくはnullを許容したい)場合は、T4のBuildメソッドの部分に直に条件を書いてしまうか、それも何だか不自然に感じる場合は生成後のファイルをT4と切り離してしまうのも良いかもですね。自由なので好きにどうぞですます。
で、本当に不変なの?
何をもってどこまでを不変というのかはむつかしいところですが、Equalsが、GetHashCodeが変化するなら、可変かしら? 単純に全ての含まれる型のゲッターが常に同一の値を返さなければ不変ではない、でも良いですが。冒頭でも言いましたが、そう見るとreadonlyだけでは不変を厳密には保証しきれていません。匿名型で例を出すと
class MyClass
{
public int i;
public override int GetHashCode()
{
return i;
}
}
static void Main(string[] args)
{
var anon = new { MC = new MyClass { i = 100 } };
var hashCode1 = anon.GetHashCode();
anon.MC.i = 1000; // 変更
var hashCode2 = anon.GetHashCode();
Console.WriteLine(hashCode1 == hashCode2); // false
Console.WriteLine(hashCode1);
Console.WriteLine(hashCode2);
}
参照しているMyClassのインスタンスの中身が変化可能で、それが変化してしまえば、違う値になってしまいます。厳密に不変であるためには、中のクラス全てが不変でなければなりません。これは今の言語仕様的には制限かけるのは無理かなー、といったところ。T4なのでリフレクションで全部バラして、参照している型が本当の意味で不変なのかどうか検証して、可変の型を含む場合はジェネレートしない、という形でチェックかけるのは原理的には可能かもしれません、が、やはり色々無理があるかなあ。
まとめ
プログラミングの楽しさの源は、書きやすく見た目が美しいことです。私はLinq to Objects/Linq to Xmlでプログラミングを学んだようなものなので、Linqの成し遂げたこと(究極のIntelliSenseフレンドリーなモデル・使いづらいDOMの大破壊)というのが、設計の理想と思っているところが相当あります。C#は言語そのものが素晴らしいお手本。匿名型素晴らしいよ(一年ぐらい前は匿名型も可変ならいいのに、とか口走っていた時期があった気がしますが忘れた、いやまあ、可変だと楽なシチュエーションってのもそれなりにいっぱいあるんですよね)。
T4の標準搭載はC#にとって非常に大きい。T4標準搭載によって、出来る事の幅がもう一段階広がった気がします。partial class素晴らしい。自動生成って素敵。T4はただのテキストテンプレートじゃなくて「VSと密接に結びついていて」「なおかつ標準搭載」「もはやC#の一部といってもいい」ことが、全く違った価値をもたらしていると思います。自動生成前提のパターンを作っても/使ってもいいんだよ、と。言語的に足らない部分の迂回策が、また一つ加わった。
見た目上若干Uglyになっても自動生成でなんとかする、というのはJava + Eclipseもそうですが、それと違うのはpartialでUglyな部分を隔離出来る(隔離によって自動生成の修正が容易になることも見逃せない)ことと、自動生成部分をユーザーが簡単に書けること、ですね。Eclipseの自動生成のプラグインを書くのは敷居が高すぎですが、T4を書く、書くまではしなくても修正する、というのは相当容易でしょう。
最近本当にT4好きですねー。色々と弄ってしまいます。こーどじぇねれーと素晴らしい。あとは、T4自体のUglyさが少し軽減されればな、といったところでしょうか。テンプレートエンジンとしてRazorに切り替えられたりを望みたいなあ。
Reactive ExtensionsのFromEventをT4 Templateで自動生成する
- 2010-08-19
Rxで面倒くさいのが、毎回書かなければならないFromEvent。F#ならイベントがファーストクラスで、そのままストリーム処理に流せるという素敵仕様なのですが、残念ながらC#のeventはかなり雁字搦めな感があります。しかし、そこは豊富な周辺環境で何とか出来てしまうのがC#というものです。F#では form.MouseMove |> Event.filter と書けますが、 form.MouseMoveAsObservable().Where と書けるならば、似たようなものですよね?
というわけで、T4です。FromEventを自動生成しましょう!と、いうネタは散々既出で海外のサイトにも幾つかあるし、日本にもid:kettlerさんがFromEventが面倒なので自動生成させてみた2として既に書かれているのですが、私も書いてみました。書くにあたってid:kettlerさんのコードを大変参考にさせていただきました、ありがとうございます。
私の書いたもののメリットですが、リフレクションを使用しないFromEventで生成しているため、実行コストが最小に抑えられています。リフレクションを使わないFromEventは書くのが面倒でダルいのですが、その辺自動生成の威力発揮ということで。それと、命名規則をGetEventではなくEventAsObservableという形にしています。これは、サフィックスのほうがIntelliSenseに優しいため。
んね?この命名規則は、RxJSのほうで公式に採用されているものなので(例えばrx.jQuery.jsのanimateAsObservable)、俺々規則というわけじゃないので普通に従っていいと思われます。
以下コード。利用改変その他ご自由にどうぞ、パブリックドメインで。
<#@ assembly Name="System.Core.dll" #>
<#@ assembly Name="System.Windows.Forms.dll" #>
<#@ assembly Name="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client\System.Xaml.dll" #>
<#@ assembly Name="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client\PresentationCore.dll" #>
<#@ assembly Name="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client\PresentationFramework.dll" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="System.Reflection" #>
<#
// 設定:ここに生成したいクラス(のTypeをFullNameで)を足してください(以下の4つは例)
// クラスによってはassemblyの増減が必要です、WPF/Silverlightなどはフルパス直書きしてください
var types = new[] {
typeof(System.Collections.ObjectModel.ObservableCollection<>),
typeof(System.Windows.Forms.Button),
typeof(System.Windows.Controls.Primitives.TextBoxBase),
typeof(System.Windows.Controls.Primitives.ButtonBase)
};
#>
using System.Linq;
using System.Collections.Generic;
<# foreach(var x in GenerateTemplates(types)) {#>
namespace <#= x.Namespace #>
{
<# foreach(var ct in x.ClassTemplates) {#>
internal static class <#= ct.Classname #>EventExtensions
{
<# foreach(var ev in ct.EventTemplates) {#>
public static IObservable<IEvent<<#= ev.Args #>>> <#= ev.Name #>AsObservable<#= ct.GenericArgs #>(this <#= ct.Classname #><#= ct.GenericArgs #> source)
{
return Observable.FromEvent<<#= ev.Handler + (ev.IsGeneric ? "<" + ev.Args + ">" : "") #>, <#= ev.Args #>>(
h => <#= ev.IsGeneric ? "h" : "new " + ev.Handler + "(h)" #>,
h => source.<#= ev.Name #> += h,
h => source.<#= ev.Name #> -= h);
}
<# } #>
}
<# }#>
}
<# }#>
<#+
IEnumerable<T> TraverseNode<T>(T root, Func<T, T> selector)
{
var current = root;
while (current != null)
{
yield return current;
current = selector(current);
}
}
IEnumerable<ObservableTemplate> GenerateTemplates(Type[] types)
{
return types.SelectMany(t => TraverseNode(t, x => x.BaseType))
.Distinct()
.GroupBy(t => t.Namespace)
.Select(g => new ObservableTemplate
{
Namespace = g.Key,
ClassTemplates = g.Select(t => new ClassTemplate(t))
.Where(t => t.EventTemplates.Any())
.ToArray()
})
.Where(a => a.ClassTemplates.Any())
.OrderBy(a => a.Namespace);
}
class ObservableTemplate
{
public string Namespace;
public ClassTemplate[] ClassTemplates;
}
class ClassTemplate
{
public string Classname, GenericArgs;
public EventTemplate[] EventTemplates;
public ClassTemplate(Type type)
{
Classname = Regex.Replace(type.Name, "`.*$", "");
GenericArgs = type.IsGenericType
? "<" + string.Join(",", type.GetGenericArguments().Select((_, i) => "T" + (i + 1))) + ">"
: "";
EventTemplates = type.GetEvents(BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.DeclaredOnly | BindingFlags.Instance)
.Select(ei => new { EventInfo = ei, Args = ei.EventHandlerType.GetMethod("Invoke").GetParameters().Last().ParameterType })
.Where(a => a.Args == typeof(EventArgs) || a.Args.IsSubclassOf(typeof(EventArgs)))
.Select(a => new EventTemplate
{
Name = a.EventInfo.Name,
Handler = Regex.Replace(a.EventInfo.EventHandlerType.FullName, "`.*$", ""),
Args = a.Args.FullName,
IsGeneric = a.EventInfo.EventHandlerType.IsGenericType
})
.ToArray();
}
}
class EventTemplate
{
public string Name, Args, Handler;
public bool IsGeneric;
}
#>
// こんなのが生成されます
namespace System.Collections.ObjectModel
{
internal static class ObservableCollectionEventExtensions
{
public static IObservable<IEvent<System.Collections.Specialized.NotifyCollectionChangedEventArgs>> CollectionChangedAsObservable<T1>(this ObservableCollection<T1> source)
{
return Observable.FromEvent<System.Collections.Specialized.NotifyCollectionChangedEventHandler, System.Collections.Specialized.NotifyCollectionChangedEventArgs>(
h => new System.Collections.Specialized.NotifyCollectionChangedEventHandler(h),
h => source.CollectionChanged += h,
h => source.CollectionChanged -= h);
}
}
}
namespace System.ComponentModel
{
internal static class ComponentEventExtensions
{
public static IObservable<IEvent<System.EventArgs>> DisposedAsObservable(this Component source)
{
return Observable.FromEvent<System.EventHandler, System.EventArgs>(
h => new System.EventHandler(h),
h => source.Disposed += h,
h => source.Disposed -= h);
}
}
// 以下略
使い方ですが、RxGenerator.ttとか、名前はなんでもいいのですがコピペって、上の方のvar typesに設定したい型を並べてください。一緒に並べたものの場合は、全て継承関係を見て重複を省くようになっています。WPFとかSilverlightのクラスから生成する場合は、assembly Nameに直にDLLのパスを書いてやってくださいな。コード的には、例によってLinq大活躍というかLinqなかったら死ぬというか。リフレクションxLINQxT4は鉄板すぎる。
一つ難点があって、名前空間をそのクラスの属している空間にきっちりと分けたせいで、例えばWPFのbutton.ClickAsObservableはSystem.Windows.Controls.Primitivesをusingしないと出てこないという、微妙に分かりづらいことになっちゃっています……。これ普通にHogeHogeExtensionsとかいう任意の名前空間にフラットに配置したほうが良かったのかなあ。ちょっと悩ましいところ。
T4の書き方
漠然と書いてると汚いんですよね、T4。読みにくくてダメだし読みにくいということは書きにくいということでダメだ。というわけで、今回からは書き方を変えました。ASP.NETのRepeater的というかデータバインド的にというかで、入れ物クラスを作って、パブリックフィールド(自動プロパティじゃないのって?そんな大袈裟なものは要りません)を参照させるという形にしました。foreachや閉じカッコ("}")は一行にする。<% %>で囲まれる範囲を最小限に抑えることで、ある程度の可読性が確保出来ているんじゃないかと思います。
といったようなアイディアは
よく訓練されたT4使いは 「何を元に作るか」 「何を作るか」 だけを考える。
何を元に作るかはきっと from ... select になるでしょう。 何を作るかの中では <#=o.Property#> で値を出力する事ができます。
csproj.user を作るための T4 テンプレート
からです。「何を元に作るか」 「何を作るか」 。聞いてみれば当たり前のようだけれど、本当にコロンブスの卵というか(前も同じこと書いた気がする)、脳みそガツーンと叩かれた感じで、うぉぉぉぉぉ、と叫んで納得でした。はい。それと、T4は書きやすいと言っても書きにくい(?)ので、囲む範囲を最小にするってことは、普通のコードでじっくり書いてからT4に移植しやすいってことでもあるんですね。
まとめ
最近F#勉強中なのです。Expert F# 2.0買ったので。と思ったらプログラミングF#が翻訳されて発売されるだとー!もうすぐ。あと一週間後。くぉ、英語にひいこらしながら読んでいるというのにー。
F#すげーなー、と知れば知るほど確かに思うわけですが、しかし何故か同時に、C#への期待感もまた高まっていきます。必ずや「良さ」を吟味して取り込んでくれるという信頼感があります、C#には。そしてまた、ライブラリレベルで強烈に何とか出来る地力がある、例えばイベントをストリームに見立てた処理には、Reactive Extensionsが登場してC#でも実現出来ちゃったり。Scalaと対比され緩やかに死んでいくJavaと比べると、F#と対比しても元気に対抗していくC#の頼もしさといったらない。
といっても、F#も全然まだ表面ぐらいしか見えてないし、突っつけば突っつくほど応えてくれる奥の深い言語な感じなので、今の程度の知識で比較してどうこうってのはないです。Java7のクロージャにたいし、Javaにそんなものはいらない、とか頑な態度を取っている人を見るとみっともないな、と思うわけですが、いつか私もC#に拘泥してC#にそんなものはいらない、的なことを言い出すようだと嫌だなー、とかってのは思ってます。進化を受け入れられなくなったら、終わり。
マルチパラダイム言語の勝利→C++/CLI大勝利ですか?→いやそれは多分違う。的なこともあるので何もかもを受け入れろ、ひたすら取り込んで鈍重な恐竜になれ(最後に絶滅する)、とは言いません。この辺のバランス感覚が、きっと言語設計にとって難しいことであり、そして今のC#は外から見れば恐竜のようにラムダ式だのdynamicだのを取り入れてるように見えるでしょうが、決してそうではなく、素晴らしいバランスに立っています。機能の追加が恐竜への道になっていない。むしろ追加によって過去の機能を互換性を保ちつつ捨てているんですよね、例えば、もうdelegateというキーワードは書くどころか目にすることもほとんどない←なのでC#を学習する場合、C#1.0->2.0->3.0->4.0という順番を辿るのは良くなくて、最新のものから降りていったほうがいい。
何が言いたいかっていったらC#愛してるってことですな。うはは。5.0にも当然期待していますし、Anders Hejlsbergの手腕には絶対的に信頼を寄せています。4.0は言語的な飛躍はあまりなかっただけに、5.0は凄いことになるに違いない。
linq.jsやRxJSのベンチマーク
- 2010-08-11
どうも、定期的linq.js - LINQ for JavaScript宣伝の会がやってまいりました。最近はページビューも絶好調、なのだけどDL数はそこまで伸びない(でも同種のライブラリよりもDL数多かったりするので需要が限界値と思われる)などなどな近況ですがこんばんわ。乱立するLinqのJavaScript実装……。などと言うほどに乱立はしてないし、そもそも2009/04に最後発で私が出したのが最後で、それ以降の新顔は見かけないのですが(しいて言えばRxJS)、ちょうどjLinqを実装した人が、ベンチ結果がボロボロだった、作り直してるという記事を出したので、ほぅほぅとそのベンチマークを見て、ちょっと改良して色々なLinq実装で比較してみました。
左のがIE8、重ねて後ろ側のがChrome。この画像は77件のJSONをGroupIDが107か185のもののみをフィルタして配列を返すという処理を1000回試行したもの。毎度思いますが、V8恐ろしく速い。そりゃnode.jsとか普通に現実的な話ですよね、大変素晴らしい。
jOrderについて
このベンチマークは、もとはjOrderという、Linq……ではなくてSQL風のもので(SQLっぽいのは結構いっぱいあります)、巨大なJSONを効率よく抽出するために、先にインデックス的なのを作ってそれから処理すれば速くなるよ!というライブラリが先々月ぐらいに出来たばっからしいのですが、それがjLinqと比較してこれだけ速いぜ!とやっていたようです。結果見る限りはjLinqクソ遅くてjOrderクソ速くて凄ー、となったのですが、なんかどーにも胡散臭さが拭えないわけですよ、ベンチ詐欺に片足突っ込んでいるというか。
jOrderは初回にインデックスっぽいものを作成するので、二回目以降の抽出は爆速、というのがウリ(っぽい)ようで、ベンチは確かに速い。で、その初回のインデックス生成は何時やってるんでしょうか?このベンチのソースを見ると、ボタンを押してからじゃなくて、ページのロード時にやってますね……。あの、それも立派なコストなのですが、無視ですか?無視ですか?そりゃあ試行回数を1000でベンチ取るならば無視出来るほどに小さいかもですね?でも、Test Cycles 1とか用意しているわけですが、どうなんでしょうね、インデックス作成時間を無視するのは、ちょっと卑怯すぎやしませんか?そもそも対象にひぢょーに遅いjLinq「だけ」を選んでいるというところがやらしい。
というわけで、オリジナルのベンチにはないのですがwith create indexというボタン押してからインデックスを作成する項目を足しました。1000回の試行では、コンセプトに乗っ取るなら1回のインデックス作成にすべきなんでしょうが、普通に1000回インデックス作成に走るのでクソ遅いです。あ、いや、別にアンチキャンペーン張ろうってわけじゃあないんですが、単純に面倒なので……。インデックス作成コストは試行回数1にすれば分かる。
ベンチ結果を見ると、まず、インデックス的なものの作成には非常にコストがかかってる。そして、わざわざコストをかけて生成したところで、Small table(77件のJSON)では、フィルタリングに関してはjQueryの$.grep、つまりは何も手をかけてないシンプルなフィルタリングと同じ速度でしかなくて、あまり意味が無い。Large table(1000件のJSON)ではそれなりな効果が出ているようですが、インデックス作成コストをペイするまでの試行回数を考えると、やはりあまり意味がなさそうな……。コンセプトは面白いんですが、それ止まりかなあ。機能的には、このインデックス生成一点勝負なところがあるので、他のLinq系ライブラリのような多機能なクエリ手段があるわけでもないし。
その他のライブラリについて
どれも似たり寄ったりで同じことが出来ますが、処理内容は全然違います。linq.jsは遅延評価であることと、列挙終了時にDisposeすることを中心に据えているので、シンプルにフィルタするだけのもの(jQueryの$.grepとか)よりも遥かに遅くなっています。JSINQも同じく遅延評価で、実装も大体似てます。なので、計測結果もほぼ同じですが、linq.jsのほうが遅い。これは、jsinqはDisposeがないため、その分の速度差が出ています(それ以外にも、単純にlinq.jsのほうが色々処理挟んでて遅め)。
LINQ to JavaScript(JSLINQ)はLINQの名を冠していますが、即時評価で、中身はただの配列のラッパーです。その分だけ単純な実装になっているので、単純なことをこなすには速い。jQueryの$.grepも同じく、普通に配列をグルッとループ回してifで弾いて、新しい配列にpushして、新しい配列を返すもの。というわけで、両者はほとんど同じ速度です。ただ、若干jQueryのほうが速いようで。これは、JSLINQはthis.itemsという形で対象の配列にアクセスしていて、それが速度差になってる模様。var items = this.itemsと列挙の前に置いてやれば、jQueryとほぼ同じ速度になる。1000回の試行だと20msecぐらいの差にはなるようですね。これが気にするほどかは、どうでしょう……。私は全く気にしません。
残念なことにめっちゃ遅いjLinqは、うーん、中はevalだらけだそうで、それが響いたそうです。と、作者が言ってるのでそうなのでしょう(適当)。RxJSも割と遅いんですが、これはしょうがないね!C#でもToObservableで変換かけたものの速度は割と遅くなるし。構造的に中間にいっぱい処理が入るので、そういうものだということで。
速度ねえ……
jLinqはさすがにアレゲなのですが、それ以外は別に普通に使う範囲ではそんな致命的に低速ってわけでもないんで、あまり気にしなくても良くね?と、かなり思ってます。linq.jsは速度を犠牲にして遅延評価だのDisposeだの入れてるわけですが、勿論、犠牲にしたなりのメリットはある(表現できる幅がとっても広がる)し。その辺はトレードオフ。配列をSelectしてToArrayするだけ、とかWhereしてToArrayするだけならば、、どうせjQueryも一緒に使うでしょ?的に考えて、jQueryの$.map, $.grepを使えば精神衛生上良いかもしれません。これは、C#で言うところのArray.ConvertAllは化石メソッドだけど、SelectしてToArrayならばConvertAllのほうが高効率なんだぜ(内心はどうでもいーんだけど)、といったようなノリで補えば良いでしょう。
それにしても、何でjQueryは$.eachの引数がmapやgrepと逆(eachだけindexが第一引数で値が第二引数)なんですかね。これ、統一してたほうが良いし、だいたいがして値が第一引数のほうが使いやすいのに。もう今更変えられない、ということなのかしらん。
そういえばで、せっかくなので「表現できる幅」の例として、ベンチには第一ソートキーにCurrency、それが重複してた場合の第二ソートキーにTotalを指定してみた例(OrderBy.ThenBy)とか(linq.js無しで書くとちょびっと面倒だよ!)、GroupIDでグルーピングした後にTotal値を合計といった集計演算(これもlinq.js無しだと面倒だよ!)とかを入れておいたので、良ければ見といてください。はい。まあ、別にこの辺はeager evaluationでも出来るというかソートもグルーピングも一度バッファに貯めちゃってるんですけどね!
まとめ
JSINQは良く出来てると思うのよ。ほんと(私はただのLinqマニアなので、基本的に他の実装は割と読んでますですよ)。ベンチ的にもlinq.jsより速いし(Disposeないからね、でもDispose使うシーンがそもそもあんまないという)、文字列クエリ式も(使わないけど)使えるし。じゃあ、JSINQじゃなくてlinq.jsがイイ!というような押しは、そこまであるかないか、どうなんでしょうね。1.メソッドの数が全然違う 2.ラムダ式的な文字列セレクターが使える 3.Dispose対応 4.RxJSにも対応 5.jQueryにも対応 6.WSHにも対応 7.VS用IntelliSense完備。ふむ、結構ありますね。というわけでlinq.jsお薦め。冒頭でも言いましたが最近のCodePlex上でのページビュー/ダウンロード数を見ると、競合のlinq移植ライブラリの中でもトップなんですよ、えへへ。まあ、4DL/dayとかいうショボい戦いなのですが。
jLinqの人が、パフォーマンス改善のついでにLinqという名前をやめてブランディングやり直すって言ってますが、きっと正しいと思う。「Linq」という名前がつく限りは「.NETの~」という印象が避けられないし、そのせいで敬遠されるというのは、間違いなくある。jLinqは、中身全然Linqじゃない独特な感じのなので、名前変えるのは、きっと良い選択。
linq.jsは100% Linqなので名前がどうこうってのはないですが、しかし、RxJSもそうなのだけど、.NET以外の人にも使って欲しいなって気持ちはとてもあります。やれる限りは頑張ってるつもりなんですが、中々どうして。JavaScriptエディタとしてのVisual Studioの使い方入門は100ブクマまであとちょい!な感じで、そういうとこに混ぜて宣伝とかいうセコい策を取ってはいるものの(いや、別にそういうつもりでやったわけでもないですが)色々と難すぃー。海外へも少しは知名度伸ばせたようなのだけど、そこでも基本的には.NET圏のみって雰囲気で、どうしたものかしらん。
つまるところ、そろそろ御託はどうでもいいから、RealWorldな実例出せよって話ですね!
テストを簡単にするほんの少しの拡張メソッド
- 2010-08-02
テストドリブンしてますか?私は勿論してません。え……。別に赤が緑になっても嬉しくないし。コード先でテスト後のほうが書きやすくていいなあ。でもそうなると、テスト書かなくなってしまって、溜まるともっと書かなくなってしまっての悪循環。
そんな普段あまりテスト書かないクソッタレな人間なわけですが(レガシーコード殺害ガイドが泣いている)、普段テスト書かないだけに書こうとすると単純なものですらイライライライラしてしまって大変よくない。しかし、それはそもそもテストツールが悪いんじゃね?という気だってする。言い訳じゃなく、ふつーにバッチイですよ、テストコード。こんなの書くのはそりゃ苦痛ってものです。
Before
例えば、こういうどうでもいいクラスがあったとします。
public class MyClass
{
public string GetString(string unya)
{
return (unya == "unya") ? null : "hoge";
}
public IEnumerable<int> GetEnumerable()
{
yield return 1;
yield return 2;
yield return 3;
}
}
ウィザードで生成されたのをベースに書くとこうなる(MSTestを使っています)
[TestMethod()]
public void GetStringTest()
{
MyClass target = new MyClass();
string unya = "unya";
string expected = null;
string actual;
actual = target.GetString(unya);
Assert.AreEqual(expected, actual);
expected = "hoge";
actual = target.GetString("aaaaa");
Assert.AreEqual(expected, actual);
}
[TestMethod()]
public void GetEnumerableTest()
{
MyClass target = new MyClass();
IEnumerable<int> expected = new[] { 1, 2, 3 };
IEnumerable<int> actual;
actual = target.GetEnumerable();
CollectionAssert.AreEqual(expected.ToArray(), actual.ToArray());
}
何だこりゃ。超面倒くさい。むしろテストがレガシーすぎて死にたい。CollectionAssertはIEnumerableに対応してないし。泣きたい。こんなの書いてられない。吐き気がする。
After
JavaScriptのQUnitは、大抵EqualとDeepEqualで済む簡単さで、それがテストへの面倒くささを大いに下げてる。見習いたい。シンプルイズベスト。ごてごてしたAssert関数なんて悪しき伝統にすぎないのではなかろうか?と思ったので、もうアサート関数なんてIsだけでいいぢゃん、ついでにactualの後ろに拡張メソッドでそのままexpected書けると楽ぢゃん、と開き直ることにしました。
[TestMethod()]
public void GetStringTest()
{
// 1. 全オブジェクトに対して拡張メソッドIsが定義されててAssert.AreEqualされる
// 2. ラムダ式も使えるので、andやorや複雑な比較などはラムダ式でまかなえる
// 3. nullはIs()で(本当はIs(null)でやりたかったのだけど、都合上断念)
new MyClass().GetString("aaaaa").Is("hoge");
new MyClass().GetString("aaaaa").Is(s => s.StartsWith("h") && s.EndsWith("e"));
new MyClass().GetString("unya").Is();
}
[TestMethod()]
public void GetEnumerableTest()
{
// 対象がIEnumerableの場合はCollectionAssert.Equalsで比較されます
// 可変長配列を受け入れることが出来るので直書き可
new MyClass().GetEnumerable().Is(1, 2, 3);
}
すんごく、すっきり。メソッドはIsだけ、ですがそれなりのオーバーロードが仕込まれているので、ほとんどのことが一つだけで表現出来ます。IsNullはIs()でいいし(表現的には分かりにくくて嫌なのですが、Is(null)だとオーバーロードの解決ができなくてIs((型)null)と書かなくて面倒くさいので、泣く泣く引数無しをIsNullとしました)し、IsTrueはIs(true)でいい。複雑な条件で比較したいときはラムダ式を渡せばいい。Is.EqualTo().Within().And() とか、全然分かりやすくないよね。流れるようなインターフェイスは悪くないけれど、別に自然言語的である必要なんて全然なくて、ラムダ一発で済ませられるならそちらのほうがずっと良い。.Should().Not.Be.Null()なんてまで来ると、もう馬鹿かと思った。
大事なのはシンプルに気持良く書けることであって、形式主義に陥っちゃいけないのさあ。
コレクション比較もIsだけですませます。IEnumerableを渡すことも出来るし、可変長引数による値の直書きも出来る。なお、Isのみなのでコレクション同士の参照比較はありません。コレクションだったら有無をいわさず要素比較にします。だって、別に参照比較したいシーンなんてほとんどないでしょ?そういう例外的な状況は素直にAssert.AreEqual使えばいい。また、CollectionAssertには色々なメソッドがありますが、それ全部Linqで前処理すればいいよね?例えばCollectionAsert.IsEmptyはAny().Is(false)で済ませられるので不要。他のも大体はLinqで何とかできるので大概不要です。
ところで、このぐらいだとウィザードが冗長というだけで
Assert.AreEqual(new MyClass().GetString("aaaaa"), "hoge");
って書けるじゃないかって突っ込みは、そのとおり。でも、少し長くなると、引数に押し込めるの大変になってきますよね。そうなると
var expected = "hoge";
var actual = new MyClass().GetString("aaaaa")
Assert.AreEqual(expected, actual);
といった具合に、変数名が必要になって大変かったるい。ので、余計な一時変数なしで流し込める方が圧倒的に楽です。そもそもに、Assert.AreEqualだと、毎回どっちがactualでどっちがexpectedだか悩むのがイライラしてしまって良くない。まあ、逆でもいいんですが。よくないんですが。
パラメータ違いのテストケース
ついでに面倒くさいのは、パラメータが違うだけにすぎない、同じようなAssertの量産。テストなんてとっとと書いてナンボなので大体コピペで取り回しちゃうわけですが、どう考えてもクソ対応です本当にありがとうございました。そういうことやると、テストの書き直しが出来なくなって身重になってしまって良くない。コードはサクッと書き直せるべきだし、テストもサクッと書き直せるべきだ。といったわけで、NUnitには属性を足すだけでパラメータ違いのテストを実行出来るそうですがMSTestにはなさそう。うーん、でも、Linqがあれば何でも出来るよ?Linq万能神理論。ということで、Linqをベースにしてパラメータ違いを渡せるクラスを書いてみました。
// コレクション初期化子を使ってパラメータを生成します
new Test.Case<int, int, int>
{
{1, 2, 3},
{100, 200, 500},
{10000, 20, 30}
}
.Select(t => t.Item1 + t.Item2 + t.Item3)
.Is(6, 800, 10050);
複数の値はTupleに突っ込めばいい。あとはSelectでactualを作って、最後にIsの可変長引数使って期待値と比較させれば出来上がり。Tupleは、C#には匿名型があるため、あまり活用のシーンがないのですが、こういうところでは便利。このTest.Caseは7引数のTupleまで対応しています(それ以上?そもそも標準のTupleの限界がそれまでなので)。使い方はnewしてコレクション初期化子でパラメータを並べるだけ。
つまるところTest.CaseクラスはただのTupleCollectionです。Tupleの配列を作るには、普通だと new[]{Tuple.Create, Tuple.Create...} と書かなければならず、死ぬほど面倒。そこで出てくるのがコレクション初期化子。これなら複数引数を受け入れるのが楽に記述できる。というわけで、コレクション初期化子を使いたいがためだけに、クラスを立てました。唯一の難点はnewしなければならない、つまりジェネリクスの型引数を書かなければならない、ということでしょうか。
そうそう、コレクション初期化子のおさらいをすると、IEnumerable<T>かつAddメソッド(名前で決め打ちされてる)があると呼び出せます。複数引数時も、波括弧で要素をくくることで対応できます(Dictionaryなどで使えるね)。
ソースコード
長々と長々してましたがソースを。Test.CaseのTupleの量産が面倒なのでT4 Templateにしました。Test.ttとかって名前にしてテストプロジェクトに突っ込んでください。中は完全に固定だから、取り回すなら生成後のTest.csを使っていくと良いかもですね。ご利用はご自由にどうぞ。パブリックドメインで。
<#@ assembly Name="System.Core.dll" #>
<#@ import namespace="System.Linq" #>
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.VisualStudio.TestTools.UnitTesting
{
public static class Test
{
// extensions
/// <summary>IsNull</summary>
public static void Is<T>(this T value)
{
Assert.IsNull(value);
}
public static void Is<T>(this T actual, T expected, string message = "")
{
Assert.AreEqual(expected, actual, message);
}
public static void Is<T>(this T actual, Func<T, bool> expected, string message = "")
{
Assert.IsTrue(expected(actual), message);
}
public static void Is<T>(this IEnumerable<T> actual, IEnumerable<T> expected, string message = "")
{
CollectionAssert.AreEqual(expected.ToArray(), actual.ToArray(), message);
}
public static void Is<T>(this IEnumerable<T> actual, params T[] expected)
{
Is(actual, expected.AsEnumerable());
}
public static void Is<T>(this IEnumerable<T> actual, IEnumerable<Func<T, bool>> expected)
{
var count = 0;
foreach (var cond in actual.Zip(expected, (v, pred) => pred(v)))
{
Assert.IsTrue(cond, "Index = " + count++);
}
}
public static void Is<T>(this IEnumerable<T> actual, params Func<T, bool>[] expected)
{
Is(actual, expected.AsEnumerable());
}
// generator
<#
for(var i = 1; i < 8; i++)
{
#>
public class Case<#= MakeT(i) #> : IEnumerable<Tuple<#= MakeT(i) #>>
{
List<Tuple<#= MakeT(i) #>> tuples = new List<Tuple<#= MakeT(i) #>>();
public void Add(<#= MakeArgs(i) #>)
{
tuples.Add(Tuple.Create(<#= MakeParams(i) #>));
}
public IEnumerator<Tuple<#= MakeT(i) #>> GetEnumerator() { return tuples.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
<#
}
#>
}
}
<#+
string MakeT(int count)
{
return "<" + String.Join(", ", Enumerable.Range(1, count).Select(i => "T" + i)) + ">";
}
string MakeArgs(int count)
{
return String.Join(", ", Enumerable.Range(1, count).Select(i => "T" + i + " item" + i));
}
string MakeParams(int count)
{
return String.Join(", ", Enumerable.Range(1, count).Select(i => "item" + i));
}
#>
オプション引数のお陰で、こういうちょっとしたのが書くの楽になりましたね(C#4.0 からの新機能)。あとは、可変長引数が配列だけじゃなくてIEnumerableも受け付けてくれれば、AsEnumerableで渡すだけの余計なオーバーロードを作らないで済むんだよね。C# 5.0に期待しますか。
まとめ
テストのないコードはレガシーコード。と、名著が言ってる(1/4ぐらいしかまだ読んでませんが!)のでテストは書いたほうがいいっす。
でも、コード書きってのは気持良くなければならない。気持ち良ければ自然に書くんです。書かない、抵抗感があるってのは、環境が悪いんです。「テスト書きは苦痛だけど良いことだから、赤が緑に変わると嬉しいから書こうぜ!」とかありえない。そんな自己啓発っぽいのは無理。というわけで、拡張メソッドで環境を変えて、気持よく生きましょうー。
JsUnit(非常にイマイチ)もそうだったんだけど、Java由来(xUnitはSmalltalkのー、とかって話は分かってます)のライブラリとかは、Java的な思考に引き摺られすぎ。もっと言語に合わせたしなやかなAPIってものがあると思うんですよね。MSTestはVS2010で、色々刷新してLinqや拡張メソッドを生かしたものを用意すべきだったと思います。C#2.0的なコードは読むのも書くのも、もう苦痛。レガシーコードとは何か?C#2.0的なコードです。いやほんと。生理的な問題で。
追記
ここで例として出したものを、より洗練させてライブラリとしてまとめました。Chaining Assertion for MSTest よければこちらもどうぞ。
Reactive Extensions入門 + メソッド早見解説表
- 2010-07-28
Silverlight Toolkitに密かに隠された宝石"System.Reactive.dll"が発見されてから1年。Reactive FrameworkからReactive Extensionsに名前が変わりDevLabsでプロジェクトサイトが公開され、何度となく派手にAPIが消滅したり追加されたりしながら、JavaScript版まで登場して、ここまで来ました。IObservable<T>とIObserver<T>インターフェイスは.NET Framework 4に搭載されたことで、将来的なSP1での標準搭載は間違いなさそう。Windows Phone 7にはベータ版の開発キットに搭載されているように、間違いなく標準搭載されそう。
現在はAPIもかなり安定したし、Windows Phone 7の登場も迫っている。学ぶならまさに今こそベスト!そんなわけで、Rxの機能の簡単な紹介と導入コード、重要そうなエッセンス紹介、そして(ほぼ)全メソッド一行紹介をします。明日から、いや、今日からRxを使いましょう。
その前にRxとは何ぞや?ですが、Linq to EventsもしくはLinq to Asynchronus。イベントや非同期処理をLinqっぽく扱えます。
Rxの出来る事
まずReactive Extensions for .NET (Rx)からインストール。そして、System.CoreEx、System.Reactiveを参照に加え(Rxにはもう一つ、System.Interactiveが含まれていて、これはEnumerableの拡張メソッド群になります)れば準備は終了。
// Rxの出来る事その1. イベントのLinq化
var button = new Button(); // WPFのButton
Observable.FromEvent<RoutedEventArgs>(button, "Click")
.Subscribe(ev => Debug.WriteLine(ev.EventArgs));
// Rxの出来る事その2. 非同期のLinq化
Func<int, int> func = i => i * 100; // intを100倍する関数
Observable.FromAsyncPattern<int, int>(func.BeginInvoke, func.EndInvoke)
.Invoke(5) // Invokeで非同期関数実行開始(Invokeは任意のタイミングで可)
.Subscribe(i => Debug.WriteLine(i)); // 500
// Rxの出来る事その3. 時間のLinq化
Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(5))
.Subscribe(l => Debug.WriteLine(l)); // 5秒毎に発火
// Rxの出来る事その4. Pull型のPush型への変換
var source = new[] { 1, 10, 100, 1000 };
source.ToObservable()
.Subscribe(i => Debug.WriteLine(i));
それぞれ一行でIObservable<T>に変換出来ます。あとは、LinqなのでSelectやWhereなどお馴染みのメソッドが、お馴染みなように使えます。そして最後にSubscribe。これは、まあ、foreachのようなものとでも捉えてもらえれば(今はね!)。
イベントをLinq化して何が嬉しいの?
合成出来るのが嬉しいよ!クリックしてクリックイベントが発動する、程度なら別にうまみはありません。でも、イベントは切り目をつけられないものも多数あります。例えばドラッグアンドドロップは「マウスダウン→マウスムーブ→マウスアップ」の連続的なイベント。従来は各関数の「外」で状態管理する変数を持ってやりくりしていましたが、Rxならば、スムーズにこれらを結合して一本の「ドラッグアンドドロップ」ストリームとして作り上げることが出来ます。逆に言えば、ただたんにイベントをLinq化しても嬉しいことはあまりありません。合成して初めて真価を発揮します。そのためには合成系のメソッド(SelectMany, Merge, Zip, CombineLatest, Join)を知る必要がある、のですがまだ当サイトのブログでは記事書いてません。予定は未定じゃなくて近日中には必ず紹介します……。
非同期をLinq化して何が嬉しいの?
それはもう自明で、単純にBeginInvoke/EndInvokeで待ち合わせるのは面倒くさいから。たった一行でラッピング出来る事の素晴らしさ!でも、同期的に書いてBackgroundWorkerで動かせばいいぢゃない。というのは、一面としては正しい。正しくないのは、Silverlightや、JavaScriptは非同期APIしか用意されていません。なので、クラウド時代のモダンなウェブアプリケーションでは、非同期と付き合うより道はないのです。
RxではBeginXxx/EndXxxという形で.NETの各メソッドにある非同期のパターンが簡単にラップ出来るようになっています。ジェネリクスの型として、引数と戻り値の型を指定して、あとはBeginInvokeとEndInvokeを渡すだけ。あの面倒くさい非同期処理がこんなにも簡単に!それだけで嬉しくありませんか?
Pull型をPush型に変えると何が嬉しいの?
分配出来るようになります。え?具体的には、C#とLinq to JsonとTwitterのChirpUserStreamsとReactive Extensionsという記事で紹介しました。そもそもPullとPushって何?という場合はメソッド探訪第7回:IEnumerable vs IObservableをどうぞ。
Rxを使うのに覚えておきたい大切な3つのこと
あまり深く考えなくても使えるけれど、少しポイントを押さえると、驚くほど簡単に見えてくる。「HotとColdの概念を掴むこと」「Schedulerが実行スレッドを決定すること」「Subjectでテストする」。この3つ。まあ、後の二つは実際のとここじつけみたいなもので、本当に大事なのはHotとColdです。あまりにも大事なのだけど、それに関して書くには余白が狭すぎる。ではなくて、以前にメソッド探訪第7回:IEnumerable vs IObservableとして書いたのでそちらで。とりあえず、ColdはSubscribeしたら即座に実行される、HotはSubscribeしても何もしないでイベント待ち。ぐらいの感覚でOKです。
Scheduler
Schedulerを使うと「いつ」「どこで」実行するかを決定することが出来ます。Rx内部でのメソッド実行は大抵このSchedulerの上に乗っかっています。
// 大抵の生成メソッドはISchedulerを受けるオーバーロードを持つ
// それに対してSchedulerを渡すと、実行スレッドを決定出来る
Observable.Range(1, 10, Scheduler.CurrentThread);
Observable.Interval(TimeSpan.FromSeconds(1), Scheduler.ThreadPool);
基本的には引数に渡すだけ。「いつ」「どこで」ですが、「いつ」に関してはRxの各メソッドが受け持つので、基本的には「どのスレッドで」実行するかを決めることになります。なお、当然デフォルト値もあるわけですが、RangeはCurrentThreadでTimerはThreadPoolだったりと、各メソッドによって若干違ったりすることに注意(但しTimerでCurrentThreadを選ぶと完全にブロックされてTimerというかSleepになるので、挙動として当然といえば当然のこと)
生成メソッドに渡す以外に、まだ使う場所があります。
// WPFでbutton1というボタンとtextBlock1というtextBlockがあるとする
Observable.FromEvent<RoutedEventArgs>(button1, "Click")
.ObserveOn(Scheduler.ThreadPool) // 重い処理をUIスレッドでするのを避けるためThreadPoolへ対比
.Do(_ => Thread.Sleep(3000)) // 猛烈に重い処理をすることをシミュレート
.ObserveOnDispatcher() // Dispatcherに戻す
.Subscribe(_ => textBlock1.Text = "clickした"); // UIスレッドのものを触るのでThreadPool上からだと例外
UIスレッドのコントロールに他のスレッドから触れると例外が発生します。でも、重たい処理をUIスレッド上でやろうものなら、フリーズしてしまいます。なので、重たい処理は別スレッドに退避し、コントロールの部品を弄る時だけUIスレッドに戻したい。という場合に、ObserveOnを使うことで簡単に実行スレッドのコントロールが可能になります。もうDispatcher.BeginInvokeとはサヨナラ!
Subjectって何?
SubjectはIObservableでありIObserverでもあるもの。というだけではさっぱり分かりません。これは、イベントのRxネイティブ表現です。なので、C#におけるeventと対比させてみると理解しやすいはず。eventはそのクラス内部からはデリゲートとして実行出来ますが、外からだと追加/削除しか出来ませんよね?Subjectはこれを再現するために、外側へはIObservableとして登録のみ出来るようにし、内部からのみ登録されたものへ実行(OnNext/OnError/OnCompleted)して値を渡します。なお、ただキャストしただけでは、外からダウンキャストすればイベントを直接実行出来るということになってしまうので、Subjectを外に公開する時は AsObservableメソッド(IObservableでラップする)を使って隠蔽します。
どんな時に使うかというとRx専用のクラスを作るとき、もしくはObservableの拡張メソッドを作る時、に有効活用出来るはずです。もしくは、メソッドを試すときの擬似的なイベント代わりに使うと非常に便利です。
// Buttonのイベントをイメージ
var buttonA = new Subject<int>();
var buttonB = new Subject<int>();
// Zipの動きを確認してみる……
buttonA.Zip(buttonB, (a, b) => new { a, b })
.Subscribe(a => Console.WriteLine(a));
buttonA.OnNext(1); // ボタンClickをイメージ
buttonA.OnNext(2); // Subscribeへ値が届くのはいつ?
buttonB.OnNext(10); // デバッグのステップ実行で一行ずつ確認
buttonA.OnCompleted(); // 片方が完了したら
buttonB.OnNext(3); // もう片方にClickが入ったときZipはどういう挙動する?
動きがよくわからないメソッドも、この方法で大体何とか分かります。Subjectには他に非同期実行を表現したAsyncSubjectなど、幾つか亜種があるのでそちらも見ると、Rxのメソッドの動きがよりイメージしやすくなります。例えばFromAsyncPatternは中ではAsyncSubjectを使っているので、AsyncSubjectの動き(OnCompletedの前後でHotとColdが切り替わる、OnNextはOnCompletedが来るまで配信されず、OnCompleted後に最後の値をキャッシュしてColdとして配信し続ける)を丁寧に確認することで、FromAsyncPatternの挙動の理解が簡単になります。
メソッド分類早見表
決して全部というわけではなく、幾つか飛ばしていますが簡単に各メソッドを分類して紹介。
生成系メソッド雑多分類
イベント(hot)
FromEvent - 文字列で与える以外のやり方もありますよ
非同期系(hot/cold)
Start - ToAsync().Invoke()の省略形
ToAsync - 拡張メソッドとしてじゃなくそのまま使うのが型推論効いて素敵
FromAsyncPattern - ToAsyncも結局これの省略形なだけだったりする
ForkJoin - 非同期処理が全て完了するのを待ち合わせて結果をまとめて返す
Enumerableっぽいの系(cold)
Range - いつもの
Return - ようするにRepeat(element, 1)
Repeat - 無限リピートもあるよ
ToObservable - pull to push
Generate - ようするにUnfold(と言われても困る?)
Using - 無限リピートリソース管理付き
Timer系(cold)
Timer - 実はcold
Interval - Timer(period, period)の省略形なだけだったり
GenerateWithTime - 引数地獄
空っぽ系(cold)
Empty - OnCompletedだけ発動
Throw - OnErrorだけ発動
Never - 本当に何もしない
その他
Defer - 生成の遅延
Create - 自作したい場合に(戻り値はDispose時の実行関数を返す)
CreateWithDisposable - 同じく、ただし戻り値はIDisposableを返す
こうしてみるとColdばかりで、Hotなのってイベントだけ?的だったりしますねー。では、IObservableの拡張メソッドも。
合成系
SelectMany - Enumerableと同じ感じですが、Rxでは合成のように機能する
Zip - 左右のイベントが揃ったらイベント発行(揃うまでQueueでキャッシュしてる)
CombineLatest - 最新のキャッシュと結合することで毎回イベント発行
Merge - Y字みたいなイメージで、左右の線を一本に連結
Join(Plan/And/Then) - Joinパターンとかいう奴らしいですが、Zipの強化版みたいな
Concat - 末尾に接続
StartWith - 最初に接続
時間系
Delay - 値を一定時間遅延させる、coldに使うと微妙なことになるので注意
Sample - 一定時間毎に、通過していた最新の値だけを出す
Throttle - 一定時間、値が通過していなければ、その時の最新の値を出す
TimeInterval - 値と前回の時間との差分を包んだオブジェクトを返す
RemoveTimeInterval - 包んだオブジェクトを削除して値のみに戻す
Timestamp - 値と通過した時間で包んだオブジェクトを返す
RemoveTimestamp - 包んだオブジェクトを削除して値のみに戻す
Timeout - 一定時間値が来なければTimeoutExceptionを出す
Connectable系(ColdをHotに変換する、細部挙動はSubjectでイメージするといい)
Publish - Subjectを使ったもの(引数によってはBehaviorSubject)
Prune - AsyncSubjectを使ったもの
Replay - ReplaySubjectを使ったもの
Enumerableに変換系(Push to Pull、使い道わかりません)
Next - MoveNext後に同期的にブロックして値が来るまで待機
Latest - 値を一つキャッシュするNext(キャッシュが切れると待機)
MostRecent - ブロックなしでキャッシュを返し続ける
例外ハンドリング系
OnErrorResumeNext - 例外来たら握りつぶして予備シーケンスへ移す
Catch - 対象例外が来たら握りつぶして次へ
Finally - 例外などで止まっても最後に必ず実行するのがOnCompletedとの違い
実行スレッド変更系
SubscribeOn - メソッド全体の実行スレッドを変える
ObserveOn - 以降に流れる値の実行スレッドを変える
クエリ系
Select - 射影(SelectManyはこっちじゃないのって話ももも)
Where - フィルタリング
Scan - Aggregateの経過も列挙するバージョン、一つ過去の値を持てるというのが重要
Scan0 - seed含む
GroupBy - グルーピング、なのだけどIGroupedObservableは扱いが少し面倒かなあ
BufferWithCount - 個数分だけListにまとめる
BufferWithTime - 一定時間内の分だけListにまとめる
BufferWithTimeOrCount - そのまんま、上二つが合わさったの
DistinctUntilChanged - 連続して同じ値が来た場合は流さない
すっとばす系
Skip - N個飛ばす
SkipWhile - 条件に引っかかる間は飛ばす
SkipLast - 最後N個を飛ばす(Lastを除いたTakeという趣向)
SkipUntil - 右辺のOnNextを察知する「まで」は飛ばす
Take - N個取る
TakeWhile - 条件に引っかかる間は取る
TakeLast - 最後N個だけを取る
TakeUntil - 右辺のOnNextを察知する「まで」は取る
Aggregate系
AggregateとかAllとかSumとかEnumerableにもある色々 - 値が確定したとき一つだけ流れてくる
変換系
ToEnumerable - 同期的にブロックしてIEnumerableに変換する、Hotだと一生戻ってこない
ToQbservable - IQueryableのデュアルらしい、完全にイミフすぎてヤバい
Start - ListなんだけどObservableという微妙な状態のものに変換する
その他
Materiallize - OnNext,OnError,OnCompletedをNotificationにマテリア化
Dematerialize - マテリア化解除
Repeat - OnCompletedが来ると最初から繰り返し
Let - 一時変数いらず
Switch - SelectMany書かなくていいよ的なの
AsObservable - IObservableにラップ、Subjectと合わせてどうぞ
疲れた。間違ってるとかこれが足りない(いやまあ、実際幾つか出してないです)とか突っ込み希望。
JavaScript版もあります
RxJSというJavaScript版のReactive Extensionsもあったりします。ダウンロードは.NET版と同じところから。何が出来るかというと、若干、というかかなりメソッドが少なくなってはいるものの、大体.NETと同じことが出来ます。SchedulerにTimeout(JavaScriptにはスレッドはないけどsetTimeoutがあるので、それ使って実行を遅らせるというもの)があったりと、相違も面白い。
JavaScriptは、まずAjaxが非同期だし、イベントだらけなのでRxが大変効果を発揮する。強力なはず、なのですが注目度はそんなに高くない。うむむ?jQueryと融合出来るバインディングも用意されていたりと、かなりイケてると思うのですがー。日本だとJSDeferredがあるね、アレの高機能だけど重い版とかとでも思ってもらえれば。
ところでObservableがあるということはEnumerableもありますか?というと、もちろんありますよ!linq.js - LINQ for JavaScriptとかいうライブラリを使えばいいらしいです!最近Twitterの英語圏でも話題沸騰(で、ちょっと浮かれすぎて頭がフワフワタイムだった)。RxJSと相互に接続できるようになっていたり、jQueryプラグインになるバージョンもあったりと、jQuery - linq.js - RxJSでJavaScriptとは思えない素敵コードが書けます。
JavaScriptはIEnumerableとIObservableが両方そなわり最強に見える。
Over the Language
Linqとは何ぞや。というと、一般にはLinq=クエリ構文=SQLみたいなの、という解釈が依然として主流のようで幾分か残念。これに対する異論は何度か唱えているけれど、では実際何をLinqと指すのだろう。公式の見解はともあれ勝手に考えると、対象をデータソースとみなし、Whereでフィルタリングし、Selectで射影するスタイルならば、それはLinqではないだろうか。
Linq to ObjectsはIEnumerableが、Linq to XmlではXElementが、Linq to SqlではExpression Treeが、Reactive ExtensionsにはIObservableの実装が必要であり、それぞれ中身は全く違う。昔はExpression Treeを弄ること、QueryProviderを実装することがLinq to Hogeの条件だと考えていたところがあったのだけど、今は、Linqの世界(共通のAPIでの操作)に乗っていれば、それはLinqなのだと思っている。
だからLinqは言語にも.NET Frameworkにも依存していない。Linqとは考え方にすぎない。例えば、Linq to Objectsはクロージャさえあればどの言語でも成り立つ(そう……JavaScriptでもね?)。むしろ重要なのは「Language INtegrated」なことであり、表面的なスタイル(SQLライクなシンタックス!)は全く重要ではない。言語に統合されていれば、異物感なく自然に扱え、IDEやデバッガなど言語に用意されているツールのサポートが得られる。(例えば……JavaScriptでガリガリと入力補完効かせてみたりね?)
言語を超えて共有される、より高い次元の抽象化としてのLinq。私はそんな世界に魅せられています。RxはLinqにおけるデータソースの概念をイベントや非同期にまで拡張(まさにExtension)して、更なる可能性を見せてくれました。次なる世界はDryad? まだまだLinqは熱い!
まとめ
ていうか改めてHaskellは偉大。でも、取っ付きやすさは大事。難しげなことを簡単なものとして甘く包んで掲示したLinqは、凄い。Rxも、取っ付きづらいFunctional Reactive Programmingを、Linqというお馴染みの土台に乗せたことで理解までの敷居を相当緩和させた。素晴らしい仕事です。
難しいことが簡単に出来る、というのがLinqのキモで、Rxも同じ。難しかったこと(イベントの合成/非同期)が簡単にできる。それが大事だし、その事をちゃんと伝えていきたいなあ。そして、Realworldでの実践例も。そのためにはアプリケーション書かなければ。アプリケーション、書きたいです……。書きます。
そういえばついでに、Rx一周年ということで、大体一年分の記事が溜まった(そしてロクに書いてないことが判明した)のと、少し前にlinq.jsのRT祭りがあった熱に浮かされて、応募するだけならタダ理論により10月期のMicrosoft MVPに応募しちゃったりなんかしました。恥ずかしぃー。分野にLinqがあれば!とか意味不明なことを思ったのですが、当然無いのでC#です、応募文句は、linq.js作った(DL数累計たった1000)と、Rx紹介書いてる、の二つだけって無理ですね明らかに。これから割と詳細に活動内容を書いて、送らなきゃいけないのですが、オール空白状態。応募したことに泣きたくなってきたよ、とほほ。
Windows Phone 7 + Reactive ExtensionsによるXml取得
- 2010-07-19
Windows Phone 7にはReactive Extensionsが標準搭載されていたりするのだよ!なんだってー!と、いうわけで、Real World Rx。じゃないですけれど、Rxを使って非同期処理をゴニョゴニョとしてみましょう。ネットワークからデータ取って何とかする、というと一昔前はRSSリーダーがサンプルの主役でしたが、最近だとTwitterリーダーなのでしょうね。というわけで、Twitterリーダーにします。といっても、ぶっちゃけただたんにデータ取ってリストボックスにバインドするだけです。そしてGUI部分はSilverlightを使用してWindows Phone 7でTwitterアプリケーションを構築 - @ITのものを丸ごと使います。手抜き!というわけで、差分としてはRxを使うか否かといったところしかありません。
なお、別に全然Windows Phone 7ならでは!なことはやらないので、WPFでもSilverlightでも同じように書けます。ちょっとしたRxのサンプルとしてどうぞ。今回は出たばかりのWindows Phone Developer Tools Betaを使います。Windows Phone用のBlendがついていたりと盛り沢山。
Xmlを読み込む
とりあえずLinq to XmlなのでXElement.Load(string uri)ですね。違います。そのオーバーロードはSilverlightでは使えないのであった。えー。なんでー。とはまあ、つまり、同期系APIの搭載はほとんどなくて、全部非同期系で操作するよう強要されているわけです。XElement.Loadは同期でネットワークからXMLを引っ張ってくる→ダウンロード時間中はUI固まる→許すまじ!ということのようで。みんな大好きBackgroundWorkerたん使えばいいぢゃない、みたいなのは通用しないそうだ。
MSDNにお聞きすれば方法 : LINQ to XML で任意の URI の場所から XML ファイルを読み込むとあります。ネットワークからデータを取ってくるときはWebClient/HttpWebRequest使えというお話。
では、とりあえず、MainPage.xamlにペタペタと書いて、MessageBox.Showで確認していくという原始人な手段を取っていきましょう。XElementの利用にはSystem.Xml.Linqの参照が別途必要です。
public MainPage()
{
InitializeComponent();
var wc = new WebClient();
wc.OpenReadCompleted += (sender, e) =>
{
var elem = XElement.Load(e.Result); // e.ResultにStreamが入ってる
MessageBox.Show(elem.ToString()); // 確認
};
wc.OpenReadAsync(new Uri("http://twitter.com/statuses/public_timeline.xml")); // 非同期読み込み呼び出し開始
}
別に難しいこともなくすんなりと表示されました。簡単なことが簡単に書けるって素晴らしい。で、WebClientのプロパティをマジマジと見ているとAllowReadStreamBufferingなんてものが。trueの場合はメモリにバッファリングされる。うーん、せっかくなので完全ストリーミングでやりたいなあ。これfalseならバッファリングなしってことですよね?じゃあ、バッファリング無しにしてみますか。
var wc = new WebClient();
wc.AllowReadStreamBuffering = false; // デフォはtrueでバッファリングあり、今回はfalseに変更
wc.OpenReadCompleted += (sender, e) =>
{
try
{
var elem = XElement.Load(e.Result); // ここで例外出るよ!
}
catch (Exception ex)
{
// Read is not supporeted on the main thread when buffering is disabled.
MessageBox.Show(ex.ToString());
}
};
例外で死にました。徹底して同期的にネットワーク絡みの処理が入るのは許しません、というわけですね、なるほど。じゃあ別スレッドでやるよ、ということでとりあえずThreadPoolに突っ込んでみた。
wc.OpenReadCompleted += (sender, e) =>
{
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
var elem = XElement.Load(e.Result);
MessageBox.Show(elem.ToString()); // 今度はここで例外!
}
catch(Exception ex)
{
// Invalid cross-thread access.
Debug.WriteLine(ex.ToString());
}
});
};
読み込みは出来たけど、今度はMessageBox.Showのところで、Invalid Cross Thread Accessで死んだ。そっか、MessageBoxもUIスレッドなのか。うーむ、世の中難しいね!というわけで、とりあえずDispatcher.BeginInvokeしますか。
wc.OpenReadCompleted += (sender, e) =>
{
ThreadPool.QueueUserWorkItem(_ =>
{
var elem = XElement.Load(e.Result);
Dispatcher.BeginInvoke(() => MessageBox.Show(elem.ToString()));
});
};
これで完全なストリームで非同期呼び出しでのXmlロードが出来たわけですね。これは面倒くさいし、Invoke系の入れ子が酷いことになってますよ、うわぁぁ。
Rxを使う
というわけで、非Rxでやると大変なのがよく分かりました。そこでRxの出番です。標準搭載されているので、参照設定を開きMicrosoft.Phone.ReactiveとSystem.Observableを加えるだけで準備完了。
var wc = new WebClient { AllowReadStreamBuffering = false };
Observable.FromEvent<OpenReadCompletedEventArgs>(wc, "OpenReadCompleted")
.ObserveOn(Scheduler.ThreadPool) // ThreadPoolで動かすようにする
.Select(e => XElement.Load(e.EventArgs.Result))
.ObserveOnDispatcher() // UIスレッドに戻す
.Subscribe(x => MessageBox.Show(x.ToString()));
wc.OpenReadAsync(new Uri("http://twitter.com/statuses/public_timeline.xml"));
非常にすっきり。Rxについて説明は、必要か否か若干悩むところですが説明しますと、イベントをLinq化します。今回はOpenReadCompletedイベントをLinqにしました。Linq化すると何が嬉しいって、ネストがなくなることです。非常に見やすい。更にRxの豊富なメソッド群を使えば普通ではやりにくいことがいとも簡単に出来ます。今回はObserveOnを使って、どのスレッドで実行するかを設定しました。驚くほど簡単に、分かりやすく。メソッドの流れそのままです。
FromAsyncPattern
WebClientだけじゃなく、ついでなのでHttpWebRequestでもやってみましょう。(HttpWebRequest)WebRequest.Create()死ね、といつも言ってる私ですが、SilverlightにはWebRequest.CreateHttpでHttpWebRequestが作れるじゃありませんか。何ともホッコリとします。微妙にこの辺、破綻した気がしますがむしろ見なかったことにしよう。
var req = WebRequest.CreateHttp("http://twitter.com/statuses/public_timeline.xml");
req.AllowReadStreamBuffering = false;
req.BeginGetResponse(ar =>
{
using (var res = req.EndGetResponse(ar))
using (var stream = res.GetResponseStream())
{
var x = XElement.Load(res.GetResponseStream());
Dispatcher.BeginInvoke(() => MessageBox.Show(x.ToString()));
}
}, null);
非同期しかないのでBeginXxx-EndXxxを使うのですが、まあ、結構面倒くさい。そこで、ここでもまたRxの出番。BeginXxx-EndXxx、つまりAPM(Asynchronus Programming Model:非同期プログラミングモデル)の形式の非同期メソッドをラップするFromAsyncPatternが使えます。
var req = HttpWebRequest.CreateHttp("http://twitter.com/statuses/public_timeline.xml");
req.AllowReadStreamBuffering = false;
Observable.FromAsyncPattern<WebResponse>(req.BeginGetResponse, req.EndGetResponse)
.Invoke() // 非同期実行開始(Invoke()じゃなくて()でもOKです、ただのDelegateなので)
.Select(res => XElement.Load(res.GetResponseStream()))
.ObserveOnDispatcher()
.Subscribe(x => MessageBox.Show(x.ToString()));
ラップは簡単で型として戻り値を指定してBeginXxxとEndXxxを渡すだけ。あとはそのまま流れるように書けてしまいます。普通だと面倒くさいはずのHttpWebRequestのほうがWebClientよりも素直に書けてしまう不思議!FromAsyncPatter、恐ろしい子。WebClient+FromEventは先にイベントを設定してURLで発動でしたが、こちらはURLを指定してから実行開始という、より「同期的」と同じように書ける感じがあって好き。WebClient使うのやめて、みんなHttpWebRequest使おうぜ!(ふつーのアプリのほうでは逆のこと言ってるのですががが)
ところで、非同期処理の実行開始タイミングはInvokeした瞬間であって、Subscribeした時ではありません。どーなってるかというと、ぶっちゃけRxは実行結果をキャッシュしてます。細かい話はまた後日ちゃんと紹介するときにでも。
バインドする
GUIはScottGu氏のサンプルを丸々頂いてしまいます。リロードボタンを押したらPublicTLを呼ぶだけ、みたいなのに簡略化してしまいました。
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Height="72" Width="200" Content="Reload" Name="Reload"></Button>
<ListBox Grid.Row="1" Name="TweetList" DataContext="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Image}" Height="73" Width="73" VerticalAlignment="Top" />
<StackPanel Width="350">
<TextBlock Text="{Binding Name}" Foreground="Red" />
<TextBlock Text="{Binding Text}" TextWrapping="Wrap" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
あとは、ボタンへのイベント設定と、Twitterのクラスを作る必要があります。
public class TwitterStatus
{
public long Id { get; set; }
public string Text { get; set; }
public string Name { get; set; }
public string Image { get; set; }
public TwitterStatus(XElement element)
{
Id = (long)element.Element("id");
Text = (string)element.Element("text");
Name = (string)element.Element("user").Element("screen_name");
Image = (string)element.Element("user").Element("profile_image_url");
}
}
public partial class MainPage : PhoneApplicationPage
{
public MainPage()
{
InitializeComponent();
Reload.Click += new RoutedEventHandler(Reload_Click); // XAMLに書いてもいいんですけど。
}
void Reload_Click(object sender, RoutedEventArgs e)
{
var req = HttpWebRequest.CreateHttp("http://twitter.com/statuses/public_timeline.xml");
req.AllowReadStreamBuffering = false;
Observable.FromAsyncPattern<WebResponse>(req.BeginGetResponse, req.EndGetResponse)
.Invoke()
.Select(res => XElement.Load(res.GetResponseStream()))
.Select(x => x.Descendants("status").Select(xe => new TwitterStatus(xe)))
.ObserveOnDispatcher()
.Subscribe(ts => TweetList.ItemsSource = ts);
}
}
実行するとこんな具合に表示されます。簡単ですねー。ただ、これだとリロードで20件しか表示されないので、リロードしたら継ぎ足されるように変更しましょう。
イベントを合成する
継ぎ足しの改善、のついでに、一定時間毎に更新も加えよう。基本は一定時間毎に更新だけど、リロードボタンしたら任意のタイミングでリロード。きっとよくあるパターン。Reload.Click+=でハンドラ足すのはやめて、その部分もFromEventでObservable化してしまいましょう。そして一定時間毎のイベント発動はObservable.Timerで。
// 30秒毎もしくはリロードボタンクリックでPublicTimeLineを更新
Observable.Merge(
Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(30), Scheduler.NewThread).Select(_ => (object)_),
Observable.FromEvent<RoutedEventArgs>(Reload, "Click").Select(_ => (object)_))
.SelectMany(_ =>
{
var req = HttpWebRequest.CreateHttp("http://twitter.com/statuses/public_timeline.xml");
req.AllowReadStreamBuffering = false;
return Observable.FromAsyncPattern<WebResponse>(req.BeginGetResponse, req.EndGetResponse)();
})
.Select(res => XStreamingReader.Load(res.GetResponseStream()))
.SelectMany(x => x
.Descendants("status")
.Select(xe => new TwitterStatus(xe))
.Reverse()) // 古い順にする
.Scan((before, current) => before.Id > current.Id ? before : current) // 最後に通した記事よりも古ければ通さない(で、同じ記事を返す)
.DistinctUntilChanged(t => t.Id) // 同じ記事が連続して来た場合は何もしないでreturn
.ObserveOnDispatcher()
.Subscribe(t => TweetList.Items.Insert(0, t)); // Insertだって...
流れるようにメソッド足しまくるの楽しい!楽しすぎて色々足しすぎて悪ノリしている感が否めません、とほほ。解説しますと、まず一行目のMerge。これは複数本のイベントを一本に統一します。統一するためには型が同じでなければならないのですが、今回はTimer(long)と、Click(RoutedEventArgs)なのでそのままでは合成出来ません。どちらも発火タイミングが必要なだけでlongもRoutedEventArgsも不必要なため、Objectにキャストしてやって合流させました。
こういう場合、Linq to Objectsなら.Cast<object>()なんですよね。Castないんですか?というと、一応あるにはあるんですが、実質無いようなもので。というわけで、今のところキャストしたければ.Select(=>(object))を使うしかありません。多分。もっとマシなやり方がある場合は教えてください。
続いてSelectMany。TimerもしくはClickは発火のタイミングだけで、後ろに流すのはFromAsyncPatternのデータ。こういった、最初のイベントは発火タイミングにだけ使って、実際に流すものは他のイベントに摩り替える(例えばマウスクリックで発動させて、あとはマウスムーブを使うとか)というのは定型文に近い感じでよく使うことになるんじゃないかと思います。SelectMany大事。
XMLの読み込み部は、せっかくなので、こないだ作ったバッファに貯めこむことなくXmlを読み込めるXStreamingReaderを使います。こんな風に、XMLを読み取ってクラスに変換する程度ならXElement.Loadで丸々全体のツリーを作るのも勿体無い。XStreamingReaderなら完全ストリーミングでクラスに変換出来ますよー。という実例。
その下は更にもう一個SelectMany。こっちはLinq to Objectsのものと同じ意味で、IEnumerableを平たくしています。で、ScanしたDistinctUntilChangedして(解説が面倒になってきた)先頭にInsert(ちょっとダサい)。これで古いものから上に足される = 新しい順番に表示される、という形になりました。XAML側のListBoxを直に触ってInsertとか、明らかにダサい感じなのですが、まあ今回はただのサンプルなので見逃してください。
RxのMergeに関しては、後日他のイベント合流系メソッド(CombineLatest, Zip, And/Then/Plan/Join)と一緒に紹介したいと思っています。合流系大事。
まとめ
驚くほどSilverlightで開発簡単。っぽいような印象。C#書ける人ならすぐにとっかかれますねー。素晴らしい開発環境だと思います。そして私は同時に、Silverlight全然分かってないや、という現実を改めて突きつけられて参ってます。XAMLあんま書けない。Blend使えない。MVVM分からない。モバイル開発云々の前に、基本的な技量が全然欠けているということが良く分かったし、それはそれで良い収穫でした。この秋なのか冬なのかの発売までには、ある程度は技術を身につけておきたいところです。
そしてそれよりなにより開発機欲すぃです。エミュレータの起動も速いし悪くないのですが、やっぱ実機ですよ、実機!配ってくれぇー。