ToArray vs ToList

LINQの結果は遅延評価なので、その場で全部評価して欲しかったりする場合などに使うToArrayとToList。どちらを使っていますか?私はToArrayのほうが好みです。と、いうのも、LINQで書く以上、長さは決まったようなものなので、これ以上AddやRemoveしたいことなんてほとんどない。勿論、必ずないとは言いませんので、その場合だけToListを使いますが、そうでない場合は、長さが固定だという意図を示すためにもToArrayが好ましい。

パフォーマンス

T[]やList<T>に変換されたあとだと、T[]のほうが、大体においてパフォーマンスは良い。という点でもToArrayがいいかなあ、と思うわけですが、それはさておき、ではToArrayとToListメソッドそれ自体のパフォーマンスはどちらのほうが良いでしょうか?理屈の上ではToListのほうが上です。というのも、変換処理は下記の図のようになっているからです。

元ソースがIEnumerable<T>である以上、長さは分からないので、ToArrayでも動的配列としていっぱいになったら二倍に拡大する、という動作を行うのはToListと変わりありません。この辺の話は動的配列への追加コストはなぜ O(1)?や、2倍だけじゃないを参考に。.NETは2倍です。そして、最後に拡大された配列の長さを整えるためコピーするのがToArray、そのまま渡すのがToList。つまり、ToArrayのほうが最後の一回のコピー動作が増えているわけです。

でも、ベンチマークをとると、ToArrayのほうが速かったりします。

// 適当さ溢れている(若干恣意的な)測り方なので、それはそれとしてくだしあ
// ToArray:00:00:01.5002685
// ToList :00:00:01.8124284
var source = Enumerable.Range(1, 100000000);

var sw = Stopwatch.StartNew();
source.ToArray();
Console.WriteLine("ToArray:" + sw.Elapsed);
GC.Collect();
sw.Restart();
source.ToList();
Console.WriteLine("ToList:" + sw.Elapsed);

へー、ToArrayのほうが速いんだー、ではなくて、要素数1億件でこの程度しかでないので、どうでもいい程度の差でしかないということです。ここ注意。こういう適当なマイクロベンチのマイクロな差で、こっちのほうが速いからこうしなければならない、これが最適化のための10箇条、みたいなことをやるのは間抜けだと思います。JavaScriptにはそういう記事があまりにも多すぎるとも思っています。

それはともかく、何で理屈の上ではコピーが多いToArrayのほうが"速い"のか。それは中身をゴニョゴニョしてみてみれば分かりますが

public static List<T> ToList<T>(this IEnumerable<T> source)
{
    // ICollection<T>の場合はnew List<T>(source)の中で最適化されてます
    // 最適化されない場合はforach(var item in source) this.Add(item) という感じ
    return new List<T>(source)
}

// 実際のコードとは違います、あくまでイメージです
public static T[] ToArray<T>(this IEnumerable<T> source)
{
    // ICollection<T>の場合はCopyToで最適化
    var collection = source as ICollection<T>;
    if (collection!= null)
    {
        var dest = new T[collection.Count];
        collection.CopyTo(dest, 0);
        return dest;
    }

    // そうでないなら列挙して配列を伸ばしながら作る
    var array = new T[4];
    var count = 0;
    foreach (var item in source)
    {
        if (array.Length == count)
        {
            var dest = new T[count * 2];
            Array.Copy(array, dest, count);
            array = dest;
        }
        array[count++] = item;
    }

    // 生成したものと長さが同じならそのまま返す
    if (array.Length == count) return array;

    // そうでないなら長さを整えてから返す
    var result = new T[count];
    Array.Copy(array, result, count);
    return result;
}

これだけだとよくわからない?うーん、そうですね。ToArrayの場合は配列を作る、それだけに最適化されていて余計なコードが一切ありません。反面、ToList、というかnew List<T>(source)は、内部では少し色々なものの呼び出し回数が多かったりしています。その辺のことが、コピー回数以上に「ほんの少しだけ」速度の差を生んでいるのではないかな、ということのようです。

// パフォーマンスを一切考えないのならこれでいいのよね
public static T[] ToArray<T>(this IEnumerable<T> source)
{
    // 実際はreturn new Buffer<T>(source).ToArray();
    return new List<T>(source).ToArray();
}

理屈的にはこれでいいわけですが、実際はBuffer<T>クラスというものをinternalでもっていて、それはLINQで使うためだけに余計なものが一切ない動的配列で、LINQ to Objectsの各メソッドは、動的配列が必要な場合ではList<T>ではなく、そのBuffer<T>クラスを使っています。DRYはどうした、という気は少しだけしますが、まあ、ユーザーとしては速いに越したことはないです。

Array.Copy

ところで、Array.CopyやICollection<T>のCopyToって面倒くさいですよね、長さを持った空の配列を作って、渡さなければならないって。と、Array Copy - Memo+の記事を見て改めて思いましたが、しかし、一番よくあるケースである一次元配列のコピーならToArrayを使えばOKです。↑の実装イメージであるように、ちゃんとis asで判定して最適化してくれているので、LINQだとforeachで全部舐めるから遅いんじゃないかなー、と考えなくても大丈夫。

まとめ

今日、Twitterで間違ったこと投稿しちゃって恥ずかすぃかったので反省して書いた。まる。とりあえずToArray使えばいいです。

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は素敵ですね!

DynamicObjectでより沢山の型情報を取る方法

Chaining AssertionのAsDynamicに少し不具合があったので、ver.1.5.0.0として更新しました。今まではメソッドの引数にnullを渡すと死んでました。ぬるぽ!まあしかし、引数そのもののからしかTypeが取れなかったので、nullだと、どのみちメソッドを特定するための型情報がないからオーバーロードの解決は不可能なので、仕様ですよ仕様、という言い訳。

などとふざけたことを思っていたのですけれど、コンパイル時に決定される引数の型を取り出す方法が判明したので、そのへんも含めて完全に解決しました。やったね。その方法は、というと

public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
    var csharpBinder = binder.GetType().GetInterface("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder");
    if (csharpBinder == null) throw new ArgumentException("is not csharp code"); // CSharpコードではない

    // ジェネリックの型引数の取得(Hoge<T>(1, t)とかでのTのこと)
    var typeArgs = (csharpBinder.GetProperty("TypeArguments").GetValue(binder, null) as IList<Type>).ToArray();
    // コンパイル時に決定されているパラメータの型の取得
    var parameterTypes = (binder.GetType().GetField("Cache", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(binder) as Dictionary<Type, object>)
        .First()
        .Key
        .GetGenericArguments()
        .Skip(2)
        .Take(args.Length)
        .ToArray();
    
    // それらの情報からMethodInfoを特定する
    var method = MatchMethod(binder.Name, args, typeArgs, parameterTypes);
    // 呼び出し
    result = method.Invoke(target, args);

    return true; // 呼べてれば必ずTrueなので。
}

……。ひどそうな匂いが!まず、素のままでは情報が足りなすぎるので、基本的にリフレクション全開です。その中でも、parameterTypesが今回追加したところです。binderのCacheに、CallSiteのデリゲート(dynamicを使って呼び出すと、コンパイル時にこの辺のものが自動生成される)があるので、そこから型情報を持ってこれることに気づいたのだ(キリッ

もう少し詳しく説明しますと

// このヘンテツもないどうでもいいコードは
dynamic d = null;
var result = d.Hoge<string, int>(100, (string)null, (ICollection<int>)null);

// コンパイル後はこんな結果に化けちゃいますあら不思議!
object d = null;
if (Program.<Main>o__SiteContainer0.<>p__Site1 == null)
{
	Program.<Main>o__SiteContainer0.<>p__Site1 = CallSite<Func<CallSite, object, int, string, ICollection<int>, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.None, "Hoge", new Type[]
	{
		typeof(string), 
		typeof(int)
	}, typeof(Program), new CSharpArgumentInfo[]
	{
		CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 
		CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.Constant, null), 
		CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.Constant, null), 
		CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.Constant, null)
	}));
}
object result = Program.<Main>o__SiteContainer0.<>p__Site1.Target(Program.<Main>o__SiteContainer0.<>p__Site1, d, 100, null, null);

細かい部分はどうでもいいので、良く分からないFuncが生成されてるんだな、というとこだけ見てもらえれば。さて、これを頭に入れた上で、DynamicObjectのTryInvokeMemberでbinderの奥底のCacheを探してみると、

このFunc6というものが、Func<CallSite, object, int, string, ICollection<int>, object>です、発見出来ました!これの型引数が、コード上での呼び出し時の型引数になるわけです。なお、第一引数はCallSite、第二引数はインスタンスなので無視してSkip(2)、そして引数の個数分だけTake(まあ、ようするに最後が戻り値の型なわけですが)。

というわけで、実際に改善されたAsDynamicを使ってみますと、

// こんな何のヘンテツもないオーバーロードのあるクラスがあるとして
public class PrivateClass
{
    private string Hoge(IEnumerable<int> xs)
    {
        return "enumerable";
    }

    private string Hoge(List<int> xs)
    {
        return "list";
    }
}

// 型はdynamicです←意地でもvarで書きたい人
var mock = new PrivateClass().AsDynamic();

// 型でオーバーロード分けが出来るようになった!
List<int> list = new List<int>();
IEnumerable<int> enumerable = new List<int>();

(mock.Hoge(list) as string).Is("list");
(mock.Hoge(enumerable) as string).Is("enumerable");

というわけで、より正確なオーバーロードの解決が図れるようになりました。何をアタリマエのことを言ってるんだお前は、と思うかもしれませんが、TryInvokeMemberに渡ってくる情報はobject[] argsなのです。args[0]の型はobjectなわけで、それをGetTypeしたら、出てくるのはList<int>なのです。何をどうやっても、.csファイルではIEnumerable<int>と書かれているという情報を得ることは出来なかったわけです、今までは。ましてやnullだったら型もヘッタクレもなかったわけです。でもこれからは違う。コード上のデータが取れる!

などとツラツラと書いてみましたが、利用者的にはどうでもいい話ですね、はい。それに、リフレクションはいいとしても、Cacheって何よ?CacheのFirstが決め打ちなのって何よ?などなどは、ぶっちゃけよくわかっていなくて(だって全部internalだしね……)若干怖いのですが、まあ、多分、大丈夫でしょう、多分……。それと、まだ完璧じゃあなくてサポートしてないオーバーロードのケースが幾つかあります。とはいえ、ほとんどのシチュエーションでは問題ないのではかと思います。

まとめ

このChaining Assertionですが、私は結構普通に使いまくっていて、ないと死にます。激しく便利。そうそう、紹介しますと、 actual.Is(expected) と、メソッドチェーン形式で流れるようにアサーションが書けます。ラムダ式での指定やコレクションへの可変長引数など、ちょうどかゆいところに手が届く拡張を施してあって、随分とテストを書くのが楽になります。

AsDynamicは、オマケ機能というか。privateなメソッドやプロパティ、フィールドにもdynamicを通してアクセス出来るように変換します。たまにしか使いませんが(MSTestにはPrivate Accessorがあるので)、あると便利よね、という時もそこそこあり。

MSTestだけではなく、NUnitやMbUnit、xUnit.NETでも使えますので&NuGet経由でも入れられますので、一度是非お試しを。

非同期の再帰的な辿り方、或いは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

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だそうです。整理されたドキュメント、多くのチュートリアルビデオ、大幅なパフォーマンス改善、そして、よくテストされた(かどうかは不明)安定版としてのライブラリ本体。全てが整いました。さあ、使いましょう!実際のプロダクトに!

また、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().Repeat().Take(3) の結果はどうでしょう?答えは、無限ループを彷徨って止まらなくなります。Emptyに対するRepeatをどう解釈するか、は正直微妙なところですけれど、元ソースが空だと死ぬというのは、結構リスキーなのではないかな、と考えてしまうのです。また、Takeをつけるのは止まることを期待するという点もあるわけですが、この場合はTakeをつけようが何しようが無駄、というのも怖い。Rxチームからの回答は、この挙動は仕様とのことでした。

最後にちょっとリクエストしてみた。今回の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)対応

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.comTwitPic 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で非同期処理を簡単には非同期中心だったので、イベントサイドについての話もしっかりまとめたいなあ、とかも思いつつ。

linq.js入門記事を書きました

お話をいただき、@ITの.NET TIPSにlinq.jsの入門記事を、二週に渡り書きました。

このサイトでやると、アレもコレもとダラダラと書いてしまって分かりづらくなっていたのですが、記事では文字数制限などのお陰で構成がすっきり、校正してもらったお陰で文章の揺れもなく。つまるところ、ほとんど編集で助けてもらったというだけで、本当にありがとうございました。サンプルコードは、コードを見ただけで伝わるよう単純に、でもlinq.jsの威力を伝えなければならないので多少の複雑さは持たなければならない。などと思い結構悩んで作りました。

お陰様で反響も結構良かったみたいでなによりです。実績も良く分からない外部ライブラリは導入できない…… という方も、@ITに載ってるから大丈夫だよ!を説得材料(?)にできるのではないでしょうか。これを機に、是非試してみてください。

ところで幾つかの話。

メソッド名大文字

失敗した。かな……。特にTojQuery()でjQueryと見かけ上(あくまで見かけだけなんですが)シームレスに繋がっていると違和感が結構あります。以前に、jQueryとLINQの世界が視覚上切れて見えるから、むしろイイぐらいなんです、とか言ってましたが勿論ただの強がりです。この辺は今更変えにくいところで、どうしたものかな、と悩んでいるところです。

パフォーマンスについて

気になりますよね?ベンチマーク的に言えば、遅い。遅延評価の実現や豊富な機能は、速度を相当犠牲にしています。では、その遅さが許容できるほどか無視できないか。これは、卑怯な逃げ口になってしまいますが、状況次第。少なくとも普通のサイトでは問題ないレベルだと思いますし、また、ブラウザのJSはどんどん速くなっていってます。Chrome/Fx4/IE9の速さ!「許容できる」の範囲はどんどん広がっていってるのでは、と。

こういった話はC#でもそうです。LINQはベタforループより確実に遅い、が、じゃあベタforループで書くかといったら、よほどエクストリームに速度を求める場合以外は書きはしません。

Pentium3でWindows XPでIE6な奴らにも配慮する!というのも確かに美学なのですが、そうではない方向も見ないと、素敵な未来はやってこないのではないかな、って。21世紀にもなるのにループアンローリングで高速化!とかいう記事ばかりが踊る世界なんて悲しいじゃないですか。

モバイル機器のことも考えなければならないし、HTML5も控え、JavaScriptで高負荷な処理をすることも少なくないので、まだまだ時代は追いついていないけれど。それでも、私はもう少し先の未来を見ていたい……。JavaScriptがこれから先、本当に一級の言語となっていくのなら尚の事です。

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メソッドのほうは、どうも上手いやり方が考えつかなくて色々と保留中。考えてはいるのですが。

C#のMicro-ORM(Dapper, Massive, PetaPoco)について

最近、巷という名の極一部で話題になっているMicro-ORMという分野。何それ?うーん、MicroなORマッパーです。まんまですが。Entity FrameworkがUltra Hugeだとしたら、その対極にあるような。定義としては「SQL本文は手書き、マッピングは自動」だけの機能を持つもの、といったところでしょうか。Microであるために「1ファイルのみ」を入れても良さそう。少なくとも、用語の発生元っぽいDapper, Massive, PetaPocoは1ファイルのみです。

他に、シンプルめなORマッパーを志している感じなのはSimple.Dataや、そしてORマッパーじゃなくてただの実行機です!と言い張る私の作っているDbExecutorなどもMicro-ORMに入れちゃってもいいかしら?Simple.Dataは名前に反してあまりシンプルではない(MEF使ってる……)感じですが。DbExecutorは十分Microです!id:taediumさんの作られているSomaも比較的シンプルめなものに入るかしら……?

この中で注目株はDapperで、Stack Overflowのエンジニアがパフォーマンスの問題を解消するために作成したもの、ということで、超巨大サイトでの利用で鍛えられているというのは何よりも信頼への担保がある。そして、この手のO/Rマッパーにはかかせない動的コード生成によるパフォーマンス向上に関しては、パフォーマンスのエキスパート中のエキスパートであるprotobuf-netの作者 Marc Gravell氏がIL生成部分を担当しているという、凄いコンビでそりゃ色々と叶わないね、という感。

パフォーマンステスト

Dapperは親切にもベンチマークプログラムも公開しているので、リポジトリにあるもの+DbExecutor、それとDataTableを追加して計測してみました。

hand coded took 53ms
Mapper Query (buffered) took 55ms
Dynamic Mapper Query (non-buffered) took 55ms
Mapper Query (non-buffered) took 56ms
Dynamic Mapper Query (buffered) took 56ms
Dapper.Cotrib took 56ms
PetaPoco (Fast) took 58ms
DbExecutor Select took 58ms
DbExecutor ExecuteReader(Hand Coded) took 58ms Dynamic Massive ORM Query took 62ms
PetaPoco (Normal) took 62ms
DbExecutor SelectDynamic took 62ms
DbExecutor ExecuteReaderDynamic(Hand Coded) 65ms
DataTable took 83ms
BLToolkit took 85ms
Simple.Data took 90ms
Linq 2 SQL Compiled took 100ms
SubSonic Coding Horror took 114ms
Entity framework CompiledQuery took 119ms
NHibernate SQL took 127ms
NHibernate HQL took 149ms
Soma took 168ms
NHibernate Criteria took 191ms
Linq 2 SQL ExecuteQuery took 215ms
Linq 2 SQL took 671ms
NHibernate LINQ took 708ms
Entity framework ExecuteStoreQuery took 726ms
Entity framework ESQL took 728ms
Entity framework No Tracking took 966ms
Entity framework took 969ms
SubSonic ActiveRecord.SingleOrDefault took 4259ms

hand codedがExecuteReaderを手で回した手書き、「Mapper Query」はDapperのことです。複数種類があるのはオプション違い。DbExecutor(太字にしています)も同様に4種類で測っています。

結果ですが、勿論Dapperは速いんですが、DbExecutorのSelectも悪くない位置にある。というか、これは普通に高速と名乗っていいレベルの速度は出てる。というか上位陣はほとんど誤差でいいんじゃないですかというところですね、実際何回か測ると若干入れ替わったりしますし。500回のループで3ms遅くて低速とか言われたら怒ります(笑)

ところでDbExecutorのDynamic類が十分すぎるほど速いのは少し驚いたり(dynamic経由だからもっとずっと遅くなるのかと思ってた……)。この計測結果を前にすると、手動マッピングするならExecuteReaderはダルいからExecuteReaderDynamic使いますねー、型変換とか不要でずっとシンプルに書けますから。Selectが使えるシーンではSelectで、柔軟なマッピングをする必要があるシーンではExecuteReaderDynamicで、というのがDbExecutorを使う場合の幸せシナリオになりそう。また、ExpandoObjectをDataTableのRowの代わりとして使うSelectDynamicも十分な速度が出ていて、これぐらい速度出るなら普通に使っちゃえますねえ。非常に良い感じ。

何故速いのか、あるいは何故遅いのか

動的コード生成しているから速い。といっても、真ん中ぐらいより上のコードはみんなやっているのではないかしら。勿論、DbExecutorも生成しています。では何でDbExecutorは大体の場合において僅差とはいえDapperより遅いのか。これはコード生成する範囲の問題です。Dapperはクエリ用に最適化してガッツシ固めて生成・キャッシュしてますが、DbExecutorは汎用的に、Typeに対して行っているので、そこで若干の差が出ています。

最初Dapperのコード見たときは、うわあ、これは凄い差がついちゃってるのでは?とか思ったんですが、蓋を開けてみると、誤差みたいに小さな差でたいしたことなかったので、このまんまで行きます(それと言い訳がましいですがCode Contractとかのハンデも若干あるので、どの程度響いているかは不明瞭ですが)。どちらにせよ、ようするところ、DBへのアクセス速度に比べれば、その程度のチューニングは大して差が出ないということでしょう。

動的コード生成も、ILでゴリゴリじゃなくてExpressionTreeを使ったゆとり全開の生成なので、それで上位にガッツリ肉薄しているのだから、十分以上です。

まとめ

DbExecutorは普通に速い。他のと比べると気が効いていて、かつ洗練されたAPIを持っていて、非常に使いやすいので、Dapperなどと比べても負けてない。Micro-ORMの最前線で全然戦えます(但し1ファイルではないけれど!)。次のアップデートでは、クエリ時の戻りが複数になる場合の対応と、ストアドプロシージャのOUTPUTの対応を予定してますので是非是非お試しを。

と、宣伝がましくなってて申し訳ないですね……。Micro-ORM自体については、まず、Entity Frameworkなど普通のORMを使わないようなら、必需品だと思います。完全手作業でデータベース触るのは馬鹿げてる。何らかの薄いラッパーぐらいは作っているだろうけれど、Dapperよりも優れていると確信持てなければ(そして多くの場合は持てるわけがない!)、そんなものは捨ててDapperを使ったほうがいいのではないかしら。いや、DbExecutorでもいいですけどね、というかDbExecutorは良いですよ。

Entity Frameworkなどを使っている場合はどうか、というと、重量級フレームワークの欠けた部分を補ってやる感じで、良い感じに使えそうです。その辺の小回りの良さ、大きめなものと一緒に使っても上手く馴染むのはMicro-ORMならではなのではかと思います。

はぢめてのWindows Phone 7でのデータベース(Linq to Sql)

Windows Phone 7に新しいSDKが来ました!9月頃リリースという話のMangoアップデート(Windows Phone 7.1)対応SDK。まだベータですが色々触れます。そしてついにデータベースが搭載されました。というわけで軽く触ってみました。

Code First

フツーだとSQLを書いてデータベースの定義を用意しなければならないところですが、WP7でのデータベースプログラミングにおいて、SQLは不要です。と、いうよりも、そもそも使えません。データベース本体(SQLCE)や、データベースにSQLを発行するクラス(ADO.NET)はMicrosoft.Phone.Data.Internalに格納されており、Internalという名のとおり、外から触ることは出来ません。ではどうするか、というと、WP7ではデータベースはLinq to Sqlを介して操作します。

じゃあテーブル定義どうするの、リレーションどうするの、というと、それはクラスから生成します。クラスを書いて、ある程度DB的に属性を付与して、CreateDatabaseとすれば、それらのテーブルを持ったデータベースが生成されます。まずコードを。あ、そうそう、System.Data.Linqの参照が別途必要です。

public class Meibo : DataContext
{
    public Meibo()
        : base("isostore:/Meibo.sdf") // connection string
    { }

    public Table<Person> Persons { get { return GetTable<Person>(); } }
}

[Table]
public class Person
{
    [Column(IsPrimaryKey = true, IsDbGenerated = true)]
    public int Id { get; set; }

    [Column(CanBeNull = false)]
    public string Name { get; set; }

    [Column]
    public int Age { get; set; }
}

名前と年齢のあるPersonというテーブルを持つ、Meiboというデータベースを定義しました。データベースはDataContextを継承し、各Tableを持つプロパティを。そしてテーブルはTable属性とColumn属性を。IsPrimaryKeyは主キー、IsDbGeneratedは自動連番、CanBeNullはnull非許可です。Columnを付けて回るのが面倒くさそうですが、まぁ単純明快ではありますねん。

接続文字列は基底クラスに渡す形で。保存場所はIsolatedStorage内です。どうせ場所固定で弄らないでしょ?と思うので、上の例では直接定義しちゃっていますが、弄りたい場合はその辺調整で。ちなみにisostore:/です。isostore://にするとダメです(最初うっかり引っかかった)。

実際に使う場合は

// 初回実行時はデータベースを作る、これはapp.xaml.csに書いておくといい
using (var db = new Meibo())
{
    if (!db.DatabaseExists())
    {
        db.CreateDatabase();
    }
}

// Insertの例
var meibo = new Meibo();

var person1 = new Person { Name = "ほげほげ", Age = 20 };
var person2 = new Person { Name = "ふがふが", Age = 15 };
var person3 = new Person { Name = "たこたこ", Age = 23 };

meibo.Persons.InsertOnSubmit(person1); // Insertする
meibo.Persons.InsertAllOnSubmit(new[] { person2, person3 }); // 複数の場合

meibo.SubmitChanges(); // SubmitChangesまではDBへの挿入はされていない

// Selectの例(Ageが20以上のものを抽出)
var query = meibo.Persons.Where(p => p.Age >= 20);

foreach (var item in query)
{
    MessageBox.Show(item.Id + ":" + item.Name + ":" + item.Age);
}

というわけで、DBの存在を全く意識せず自然に書けます。実に素晴らすぃー。

リレーション

リレーションも勿論張れます。例はマクドナルドのバーガーの価格表で。地域で価格が違うので、Burger(バーガー名)、Price(値段)、Place(地域)の3つをBurger-Price-Placeで関連付けてきませう。

public class McDonald : DataContext
{
    public McDonald()
        : base("isostore:/McD.sdf")
    { }

    public Table<Burger> Burgers { get { return GetTable<Burger>(); } }
    public Table<Price> Prices { get { return GetTable<Price>(); } }
    public Table<Place> Places { get { return GetTable<Place>(); } }
}

[Table]
public class Burger
{
    [Column(IsPrimaryKey = true, IsDbGenerated = true)]
    public int Id { get; set; }
    [Column]
    public string Name { get; set; }

    [Association(Storage = "_Prices", OtherKey = "BurgerId")]
    public EntitySet<Price> Prices
    {
        get { return this._Prices; }
        set { this._Prices.Assign(value); }
    }
    private EntitySet<Price> _Prices = new EntitySet<Price>();
}

[Table]
public class Price
{
    [Column(IsPrimaryKey = true, IsDbGenerated = true)]
    public int Id { get; set; }
    [Column]
    public int BurgerId { get; set; }
    [Column]
    public int PlaceId { get; set; }
    [Column]
    public int Value { get; set; }

    [Association(IsForeignKey = true, Storage = "_Burger", ThisKey = "BurgerId")]
    public Burger Burger
    {
        get { return _Burger.Entity; }
        set { _Burger.Entity = value; }
    }
    private EntityRef<Burger> _Burger = new EntityRef<Burger>();

    [Association(IsForeignKey = true, Storage = "_Place", ThisKey = "PlaceId")]
    public Place Place
    {
        get { return _Place.Entity; }
        set { _Place.Entity = value; }
    }
    private EntityRef<Place> _Place = new EntityRef<Place>();
}

[Table]
public class Place
{
    [Column(IsPrimaryKey = true, IsDbGenerated = true)]
    public int Id { get; set; }

    [Column]
    public string Name { get; set; }

    [Association(Storage = "_Prices", OtherKey = "PlaceId")]
    public EntitySet<Price> Prices
    {
        get { return this._Prices; }
        set { this._Prices.Assign(value); }
    }
    private EntitySet<Price> _Prices = new EntitySet<Price>();
}

ぎゃー。面倒くさ。本来のLinq to Sqlではデータベースが先にあって、そこから機械生成でこれを作るんですが、コードを先で作るのはちょっと骨が折れます。Entity Framework Code Firstは、コードを先に作るのが大前提だけあって書きやすいように色々調整してある感じですが、WP7/Linq to Sqlは、本当にただただ手で書きますというわけで全くイケてない。

さて、リレーションはAssociation属性でつけます。また、多を辿る場合はEntitySet、一を辿る場合はEntityRefのプロパティを用意します。これがまた面倒くさ……。たいしたことはない機械的作業ですが、自動プロパティで済ませられないとウザったいことこの上なく。コードスニペットでも用意しますかねえー。

しかし苦労するだけの価値は、あります!

まずデータを用意しなきゃということでInsertを。

// Insertの例
var mcd = new McDonald();

var hamburger = new Burger() { Name = "ハンバーガー" };
var blt = new Burger() { Name = "ベーコンレタストマト" };

var kanto = new Place() { Name = "関東" };
var qshu = new Place() { Name = "九州" };
var hokkaido = new Place() { Name = "北海道" };

var prices = new[]
{
    new Price { Burger = hamburger, Place = kanto, Value = 100 },
    new Price { Burger = hamburger, Place = qshu, Value = 150 },
    new Price { Burger = hamburger, Place = hokkaido, Value = 160 },
    new Price { Burger = blt, Place = kanto, Value = 250 },
    new Price { Burger = blt, Place = qshu, Value = 230 },
    new Price { Burger = blt, Place = hokkaido, Value = 220 }
};

mcd.Places.InsertAllOnSubmit(new[] { kanto, qshu, hokkaido });
mcd.Burgers.InsertAllOnSubmit(new[] { hamburger, blt });
mcd.Prices.InsertAllOnSubmit(prices);

mcd.SubmitChanges();

リレーションを軽やかに片付けて、挿入してくれます。実に自然でイイ!それに、こういうののinsert文手書きはカッタルイですからねえ。更にSelectは

var mcd = new McDonald();

// 関東のバーガーのNameとPriceを抽出
var query = mcd.Burgers.Select(b => new
{
    b.Name,
    Price = b.Prices.First(p => p.Place.Name == "関東").Value
});

// ハンバーガー:100, ベーコンレタス:250
foreach (var item in query)
{
    MessageBox.Show(item.Name + ":" + item.Price);
}

// なお、IQueryable<T>をToStringすると手軽に発行されるSQLが確認出来る
// もう一つの手はDataContext.Logから取ること
MessageBox.Show(query.ToString());

コード上にjoinはないけど、発行されるSQLはjoinしています。手動でjoinすることも可能ですが、基本的にはオブジェクト間をドットで辿って操作します。その方が自然に書けるし、何より、楽ですもの。

DataContextのDispose

usingで括ってあげるのが礼儀正しいわけですが、WP7では実際どう考えるべきだろう。サンプル見てると、CreateDatabaseやSchemaUpdateではusingで囲んでますが、そうでない普通の操作ではコードビハインド内でDataContext使い回してるんですね。基本的にIsolatedStorageに隔離されているわけだし、画面外に出るときだけ切った繋げたすればいいのかなあ、といったふうに思いましたがどうなのでしょ。

.NET版との差分

ほとんど.NET版のLinq to Sqlと同じなのですが、若干追加があります。一つはデータベースのスキーマのアップデート。Microsoft.Phone.Data.Linq Namespace名前空間の参照で、DataContextにCreateDatabaseSchemaUpdaterが追加されます。これにより、アップデートなどによるテーブル構造の変化にも対応出来ます。もう一つは IndexAttribute Class

これらは、通常Linq to Sqlが用いられていたデータベースからのクラス自動生成じゃなく、クラスからのデータベース生成になったことにより、テーブル作りに足りていなかった面の補足と見れるかな。また、その逆で.NET版でサポートされているけれど、WP7版にはないものも幾つかあります。詳しいリストはMSDNのLINQ to SQL Support for Windows Phoneを見ればいいんじゃないかな、ということで。

学習リソース

若干の差異はあるとはいえ、Linq to SqlはLinq to Sqlなので、MSDN - LINQ to SQLを見るのが良いでしょう。また、慣れない間はWP7版ではなく.NET版で、ConsoleApplicationで挙動をあらかた確認しておいたほうが、スムースに行くかとは思います。属性貼ったりは、結構面倒だし罠もあるところですからね……。

まとめ

諸君らの愛したLinq to Sqlは死んだ!何故だ!そうしてEntity Frameworkに置き換えられる運命を辿ったLinq to Sqlですが、ここにきて華麗に復活するとは誰も予想だにしなかったところで、こういう展開は面白い。そして生SQLが使えないのは英断。縛りではあるのですが、Phoneでのアプリケーションの9.9割は、生SQLを必要とすることはないのではないか、とも。

生SQL触れるだろうと思ってWP7版も作るぜ!な勢いで用意していたDbExecutorのWP7版は永劫さようならになってしまいましたががが。DbExecutorはDbExecutorで、もう少し機能追加しますがー。

ところでMangoで、他に追加されたクラスを少し。System.Reflection.Emitが追加されました。これはILを直弄りして動的コード生成するためのクラスですが、WP7でIL生成とかヤラネーヨ。というわけでもなくはなく実は有益。シリアライザの高速化のために動的コード生成は常套手段となっているので、自分は直に使わなくても、普通にメリットは大きく。例えばJSON.NETのJSONシリアライズ/デシリアライズは、WP7版だけリフレクションを直に使ったもので見たところ遅そうでしたが、恐らく次からは.NET版と同じく動的コード生成になり、高速化されるでしょう。ORマッパーなどもそうです。そう、Linq to SqlでもMetaAccessorクラスなどの辺りを覗いてみれば、ILをEmitしているコードが見えます。

そういえばLambdaExpressionもCompile出来るようになりました。が、AssignやLoopなどは搭載されていません、ぐぬー。コード生成したい人はExpressionTreeでお手軽、ではなく、まだまだILGeneratorでEmit頼りしかなさそうです。更に言えばExpressionVisitorも入っていませんね。SL4に近くなったけれどSL4とは言えない、WP7はWP7としか言いようのないAPIになってまいりました。

基礎からのCode Contracts

23日にCode Contractsについてのセッションを行いました。

ufcppさんのレポートとセッション資料は第7回日本C#ユーザー会 勉強会レポート - 日本 C# ユーザー会に。Code Contractsを説明する場合、通常は背後にあるDesign By Contractの説明をしてから流れると思うのですが、今回はufcppさんが前の時間で受け持ってくれたので、その辺は完全スルーで.NET上のCode Contractsの話のみにフォーカスしています。

標準入りしているようだけど何なの?→実際は標準入りとは全然言えません というところから入って、主眼はバイナリリライター、ということで、セッションでは.NET Reflectorを片手にどのようにリライトされるかを見ていきました。どう展開されるのかをそうして確認すると、仕組みが理解しやすいのではないかと思います。Reflector有料化ということで、代替も色々出てきているようですが、私はReflector使い続けますね。他のはまだ低速だったりと色々引っかかるところが多いので。それとまあ、恩返しというか、今までお世話になっていますし。

Code ContractsはDevLabsの中でも、Axumは死亡が確定(Blogに開発チームオワタと書いてあった、ページから消えるのも時間の問題?)だし、Dolotoは明らかに開発止まってて何故残り続けているのかが分からないぐらいだし、というわけで最古株となっていますね。GT先輩いつ卒業するの!(やっとしました!)という感じな某車ゲーを少し思い起こしたり。卒業出来る日は来るのでしょうか。

それにしてもDevLabsはTC Labsばかりとなってしまい、何かツマラナサも感じてしまうような。というかtcのロゴの3って、最初にリリースされたSho, Dataflow, Dryadと3つだから3なのかと思ってましたが、最近Solver Foundationも入ってしまって4じゃねーか、という感。ていうかSolver Foundationは結構イケてるロゴがあるので、別にTCを冠しなくても良かったような。DryadはDryadで、'Dryad' becomes 'LINQ to HPC' !だそうで、色々整理しきれてない感。

ところでスライド作りは結構楽しくはあるんですが、やっぱ大変ですねー。コードはコンパクトでなければならないし、全体的にしっかり流れが整ってなければならないし。かなり考えますが、そのお陰で、私の普段のだらだらブログ記事よりは分かりやすさアップしてるかな?というところです、前回のRxのスライド - Reactive Extensionsで非同期処理を簡単にもそうでしたが。

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…… 名前だけなら聞いたことがある、いや、名前も聞いたことない何それ、ぐらいからが対象なのぜ、是非どうぞ。もう使っている、という人には物足りないかも(むしろそこは私が教えて欲しいもがもがもがもが)。

どちらも、まだ参加申し込み出来るようなので是非聞きにきてください。

Prev | | Next

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(.NET)
April 2011
|
July 2027

X:@neuecc GitHub:neuecc

Archive