Chaining Assertion 1.7.0.1 - 値比較の追加

EqualsやGetHashCodeをオーバーライドするかと言ったら、そういう目的があるなら当然するし、そうでないならしない。という極当たり前なところに落ち着いたりはする。目的ってどういう時かといったら、LINQでDistinctの対象にしたい時とかですかね、まあ、よーするに値で比較したい!時です、まんまですね。

なので、逆にそれ以外の用途であえてこれらをオーバーライドすることはないです。特にテストのためにオーバーライドというのは、はっきしいって、良くないって思ってます。Equalsというのはクラスとして非常に重要な意味のあるところなので、そこにテスト都合が混じりこむのはNGです。

でも、テスト都合で値で比較したかったりは割とあるんですよね。じゃあどうするかって、まあ、ふつーにアサート側で構造比較したほうがいいでしょ、テスト都合なんだから。QUnitなんてdeepEqualが基本で、それが存外使いイイんですよ。

と、いうよくわからない前振りですが、つまるところChaining Assertionに値で比較できるIsStructuralEqualを足しました。

いやあ、一年ぶりの更新です!というか、もう一年前ですかー、早いものだ。もうAssert.ThatをDisるのも忘れてたぐらいに昔の話ですねー、あ、今も当然Assert.ThatとかFluent何ちゃらは嫌いですよ、と、それはさておき。かなり前の話なのでChaining Assertionについておさらい。メソッドチェーンな感じにさらさらっとAssertを書けるテスト補助ライブラリです。詳しくはメソッドチェーン形式のテスト記述ライブラリという1.0出した時の説明をどうぞー。主にMSTestやNUnitに対応しています。勿論NuGetでも入りますのでChainingAssertionで検索を。

今回追加したのはIsStructuralEqual(もしくはIsNotStructuralEqual)で、構造を再帰的に辿って値としての一致で比較します。

// こんなクラスがあるとして
class MyClass
{
    public int IntProperty { get; set; }
    public string StrField;
    public int[] IntArray { get; set; }
    public SubMyClass Sub { get; set; }
}

class SubMyClass
{
    public DateTime Date { get; set; }
}


[TestMethod]
public void TestMethod1()
{
    var mc1 = new MyClass
    {
        IntProperty = 100,
        StrField = "hoge",
        IntArray = new[] { 1, 2, 3, 4, 5 },
        Sub = new SubMyClass
        {
            Date = new DateTime(1999, 12, 31)
        }
    };

    var mc2 = new MyClass
    {
        IntProperty = 100,
        StrField = "hoge",
        IntArray = new[] { 1, 2, 3, 4, 5 },
        Sub = new SubMyClass
        {
            Date = new DateTime(1999, 12, 31)
        }
    };

    mc1.IsNot(mc2); // mc1とmc2は全て同じ値ですが、参照比較では当然違います

    mc1.IsStructuralEqual(mc2); // IsStructuralEqualでは全てのプロパティを再帰的に辿って比較します
}

なんのかんので便利で使っちゃいますね、多用しちゃいますね、きっと。ちなみに、ただたんに値比較したいだけなら、JSONにでもDumpして、文字列一致取ればいいだけなんですけれど。自前で辿ることによって、誤った箇所へのメッセージは割と親切かな、と思います。あとIEquatableの扱いとか型の扱いとか、ただのDumpとは色々若干と違うので、まあ、こちらのほうが望ましい具合な結果が得られると思います。

var mc1 = new MyClass
{
    IntProperty = 100,
    StrField = "hoge",
    IntArray = new[] { 1, 2, 3, 4, 5 },
    Sub = new SubMyClass
    {
        Date = new DateTime(1999, 12, 31)
    }
};

// 間違い探し!
var mc2 = new MyClass
{
    IntProperty = 100,
    StrField = "hoge",
    IntArray = new[] { 1, 2, 3, 100, 5 }, // 4番目が違う
    Sub = new SubMyClass
    {
        Date = new DateTime(1999, 12, 31)
    }
};

// 以下のようなエラーメッセージが出ます
// is not structural equal, failed at MyClass.IntArray.[3], actual = 4 expected = 5
mc1.IsStructuralEqual(mc2);

// こんどはStrFieldが違う
var mc3 = new MyClass
{
    IntProperty = 100,
    StrField = "hage",
    IntArray = new[] { 1, 2, 3, 4, 5 },
    Sub = new SubMyClass
    {
        Date = new DateTime(1999, 12, 31)
    }
};

// 以下のようなエラーメッセージが出ます
// is not structural equal, failed at MyClass.StrField, actual = hoge expected = hage
mc1.IsStructuralEqual(mc3);

割と十分、分かりやすい。かな?

コードは結構好き勝手感です。PropertyInfoとFieldInfoは共にMemberInfoを継承してるが、GetValueは同じメソッドシグネチャだけど、MemberInfoに定義されてるわけじゃなくてPropertyInfo, FieldInfoにそれぞれあるから共通でまとめられないよー→dynamicで受ければGetValue使えて大解決。とか、まあふつーのコードではやらないようなことも、UnitTest用だから若干の効率低下は無視でなんでもありで行くよ!というのが割と楽しいですね。

その他

ついでにjsakamotoさんからPull Request来ていたIsInstanceOfでメソッドチェーンできるようになったり(Pull Requestの放置に定評のある私です!というか初めてacceptしたわー)、IsTrueとIsFalseを足した(いやあ、Is(true)ってやっぱ面倒くさかったよ、あはは)りなどしました。

さて、お次はWinRT対応とWindows Phone 8対応を、といったところなのですが、それはそのうち近いうちに!WinRT対応はねえ、リフレクション回りがドサッと変わってるので面倒といえば面倒なんですよねえ。まあ、勉強のためのちょうどいい題材ではあるので、手を付けたいとは思ってるのですけれど。

あとね、正直NUnitはいいとしてもMbUnitやxUnit、SLやWP7に対応させるの超面倒くさい。やりすぎた。これのせいでちょっとした修正ですら大仕事なわけですよ。このメンテコスト最悪すぎる状態がどうにもねえ、それでいてNUnitはともかく、その他なんてほとんど使われてないですからねえ。分かってはいたのですが、こうシンドイと結構限界。というわけで、WP7とSilverlightは削除しました。この二つはもういらないぢゃん?さようなら……。

ああ、あとFakes FrameworkのためのVerifierも入れたいしねえ、やりたいことは割と多いんですが、ニートもこれはこれでいて忙しくて手が回らないのですよー。

Microsoft Fakes Frameworkの使い方

Fakes FrameworkはVisual Studio 2012から搭載されたユニットテスト用のもっきゅっきゅライブラリです。いや、ライブラリというには大掛かりなので、やっぱFrameworkでしょうか。ともあれ、そんなもののようなものです。ドトネトだと競合で最も有名なのはMoqですね。競合との大きな違いは、通常のもっきゅっきゅライブラリがinterfaceやvirtualなメソッド類しか上書きできないのに対して、FakesはStaticメソッドやふつーの非virtualメソッドすらも上書き出来ちゃうところにあります。つまり、なんでもできます。

そして、Visual Studio Ultimateじゃないと使えません。……うぉーん。と、いうわけで、強力さはよーく分かるんですが、Ultimateでしか使えないところに萎えていたりしました。が、Visual Studioへの要望を出すForumでProvide Microsoft Fakes with all Visual Studio editionsといった投票が以前からあり(私もVote済みです)、そこでついに最近、全エディションに搭載するよう検討するから待っててね!というMSからの返答が!やったね!あ、まだVoteしてない人はVoteしましょう。

さて、Fakesは元々Molesという名前で、PexというMicrosoft Researchで開発されていた(今はメンテされてるのかなあ、怪しいなあ)自動テストツールの付属品みたいな感じで存在していました。できることは、既存のクラスの静的/インスタンスメソッドやプロパティの動作を、自由に置き換えることです。もうこれ本当に素晴らしくて、一度使うとMoles抜きのテストとか考えられないぐらいで、このサイトでもRx + MolesによるC#での次世代非同期モックテスト考察とかRxとパフォーマンスとユニットテストとMoles再びといった記事で紹介してきました。どちらもRxとセットで書いていますが、Moles自体は別にRx関係ありません。

ちなみに同様のことができるライブラリにはTypemock IsolatorJust Mockがありますが、何れも有償です(結構お高い、まぁVisual Studio Ultimateほどではないですが!)。Fakesとそれら(やMoq)の違いはもうひとつあって、Fakesは自動生成が基盤になっているので、メソッドやプロパティの置き換えが同様の定義をラムダ式で渡すだけという、非常にスムーズなやりかたで済みます。他のものは、基本的にはSetUp.Returnsとか、流れるようなインターフェースが基調になっていて、そんな書きやすいわけではないんですね。機能が強力だという他に、モック定義が超簡単、というのもFakesの大きな魅力です。

使い方

詳細な使い方とかガイドはIsolating Code under Test with Microsoft Fakesにありますが、まあ簡単に見てきましょうか。ユニットテストプロジェクトの参照設定でSystemを右クリックしてFakesアセンブリに追加をクリック。

するとFakesフォルダの下にmscorlib.fakesとSystem.fakesが作られます。そして、暫く待つとmscorlib.4.0.0.0.FakesとSystem.4.0.0.0.Fakesが追加されます。これ、バックグラウンドに必死に解析しているといった感じなので、割と待たされます(せめてステータスバーで通知してくれてれば分かりやすいのですが)。すぐにFakesが追加されなくてオカシイなー、とかドーナッテンダー、とか思うかもですが、まあゆるりと待ちましょう。待つといっても1分は待たないかな、さすがに、マシン性能にもよるでしょうが。

これでとりあえず準備完了。

一番単純かつよく使うかつ有意義かつそこらじゅーで紹介されている例としては、DateTime.Nowの差し替えなので、まずそれを見ますか、定番お馴染みですけれど。Assertには別ライブラリのChaining Assertionを使います。Assert.AreEqual(25, Math.Pow(5, 2))がMath.Pow(5, 2).Is(25)といったようにメソッドチェーンでサクッと書けて可読性良くて実にいい(宣伝)。

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        // Shim"s"Contextで囲むとその中でShim使える(Stubだけ利用なら不要)
        using (ShimsContext.Create())
        {
            // DateTime.Nowを1999年12月31日に差し替え!
            ShimDateTime.NowGet = () => new DateTime(1999, 12, 31);

            // なのでDateTime.Nowは1999年です!
            DateTime.Now.Year.Is(1999);
        }
    }
}

どーでもいーんですがShimContextと間違えて、でてこないなあ、と悩んだりはよくしてました。正しくはShimsContextですねん。ともあれ、超簡単に難問であるDateTimeの差し替えに成功しました!素晴らしい!

さて、もっきゅっきゅライブラリによくある機能はもう一つ、差し替えたメソッドが呼ばれたかどうかの検証があります。これに関してはFakesは特にライブラリ側でサポートはしていません。自前でやります。例えば……

var calledCount = 0;
var stub = new StubIEnumerable<int>
{
    GetEnumerator = () => { calledCount++; return Enumerable.Range(1, 10).GetEnumerator(); }
};

stub.Count().Is(10); // LINQのCountを使ってGetEnumeratorを呼んだ

calledCount.Is(1); // 1回呼ばれた、という検証

StubはふつーのMoqライブラリで定義可能なのと同じで、interfaceかvirtualなメソッドを置き換えられます。ラムダ式で定義出来るのが、やっぱ簡単でイイですね。で、検証のやり方は、単純に外部に変数定義してそれ呼んでやって、という地味ーで原始的な手法が正解。手間といえば手間ですが、Moq定義がシンプルなので、違和感は全然ないです。

Verify用拡張の実装

とはいえ、定形パターンでラムダの外に変数置いてどうこう、というのも面倒くさいので、Verify用にちょっと作ってみました。例えばこんな風に使います。

[TestMethod]
public void IListUseCountByLINQ()
{
    var enumerator = Verifier.Zero("IList.GetEnumerator"); // 文字列入れておくとエラー時にどの検証で失敗したのか判別できる。
    var count = Verifier.Once(); // 省略も可能だけどエラー時に不明になるので、メソッド内で検証は一個ののみとか限定で。

    IEnumerable<int> list = new StubIList<int>()
    {
        // 各メソッド先頭でCalledを呼ぶと内部のカウンターがIncrementされる
        GetEnumerator = () => { enumerator.Called(); return Enumerable.Empty<int>().GetEnumerator(); },
        CountGet = () => { count.Called(); return 10; },
    };

    list.Count(); // LINQのCount()メソッドを使う

    Verifier.VerifyAll(count, enumerator); // Countは一度呼ばれてGetEnumeratorは一度も呼ばれてないことの検証実行
}

もしこれでvar enumerator = Verifier.Once("IList.GetEnumerator") にすると、VerifyAllのところで「System.Exception: Verify Error - Key:IList.GetEnumerator, Condition:(x == 1), CalledCount:0」という例外が発生して、実行されたかの検証が行える、みたいな感じですん。エラーメッセージもそこそこ親切。

ちょっと面倒かなあ、いちいち変数定義するのダルいなあ、とかとも思いますが、まあ何もないよりは良いのではないでしょうか。以下はその実装。

public class Verifier
{
    public static Verifier Zero(string key = "")
    {
        return new Verifier(key, x => x == 0);
    }

    public static Verifier Once(string key = "")
    {
        return new Verifier(key, x => x == 1);
    }

    public static Verifier Create(Expression<Func<int, bool>> condition)
    {
        return new Verifier("", condition);
    }

    public static Verifier Create(string key, Expression<Func<int, bool>> condition)
    {
        return new Verifier(key, condition);
    }

    public static void VerifyAll(params Verifier[] verifier)
    {
        foreach (var item in verifier)
        {
            item.Verify();
        }
    }

    readonly Expression<Func<int, bool>> condition;
    int count;
    public int Count { get { return count; } }
    public string Key { get; private set; }

    private Verifier(string key, Expression<Func<int, bool>> condition)
    {
        this.Key = key;
        this.count = 0;
        this.condition = condition;
    }

    public void Called()
    {
        Interlocked.Increment(ref count);
    }

    public void Verify()
    {
        if (!condition.Compile().Invoke(count))
        {
            var msg = string.Format("Key:{0}, Condition:{1}, CalledCount:{2}", Key, condition.Body, count);
            throw new Exception("Verify Error - " + msg); // 例外は最終的に独自例外を使う
        }
    }
}

複数回呼ばれることの検証はVerifier.Create(x => x == 10)とかVerifier.Create(x => x >= 1)とかって書きます。ここをTimes.ExactlyだのTimes.AtMostOnceだのTimes.Betweenだのとメソッド名でやりくりさせる流れるようなインターフェース(笑)的なやり方は嫌いですねえ(Timesは別に流れてませんが)。ラムダ式あるんだからそれ使うべきでしょ常識的に考えて。

これはただのコンセプトですが、もう少し練りこんだらChaining Assertionに入れましょう。

WebRequestのShimを作りたい場合

ところで、mscorlibとSystemのFakeが標準で作られるわけですが、それの中身、少ないですよね?WebClientはないし、WebRequestもStubばかりでShimがないし。どうなってるの?

mscorlibとSystemは巨大なライブラリなため、全てのFakeを作っていると量が膨大すぎて処理に時間がかかります。だから、デフォルトでは生成されるものが限定的になっています。じゃあどうすればいいのか、というと、.fakesの中身(XML)を編集して、明示的に生成するものを指定してあげれば解決します。

<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/">
    <Assembly Name="System" Version="4.0.0.0"/>
    <ShimGeneration>
        <Add FullName="System.Net.HttpWebRequest" />
    </ShimGeneration>
</Fakes>

Visual Studioで編集すればIntelliSenseが効くので、迷いなくできるでしょう。StubGenerationに対するオプションがあったり、Disableのtrue/falseが指定できたりとか、IntelliSenseに従うだけで発見できます。書き換えたらビルドすれば、設定の反映されたDLLに置き換えられます。もし置き換わらなかったら、テストプロジェクトのFakesAssembliesフォルダの中身を全部消して再ビルドしてみましょう。それでも追加されていなかったら、.fakesの書き換えミスでしょうね。私はFullNameとTypeNameを間違って追加されねー、と悩んだりしたことあります。

さて、じゃあ実際に↑のHttpWebRequestへのShimを使って、例えばHttpClientは最終的にWebRequestで実行されてるんだー、というのを検証するには……

// 非同期メソッドをテスト対象にする時はTaskを戻り値にする
[TestMethod]
public async Task HttpClientIsWrapperOfHttpWebRequest()
{
    using (ShimsContext.Create())
    {
        var v = Verifier.Once();

        // どこかで生成される全てのInstanceを対象にするには.AllInstances経由で
        // 第一引数はそのインスタンスそのものがくる
        ShimHttpWebRequest.AllInstances.BeginGetResponseAsyncCallbackObject = (instance, callback, state) =>
        {
            v.Called();
            // ExecuteWithoutShimsで差し替えていないオリジナルのものを呼べる
            return ShimsContext.ExecuteWithoutShims(() => instance.BeginGetResponse(callback, state));
        };

        await new HttpClient().GetAsync("http://google.co.jp/");

        v.Verify();
    }
}

といったように書けました。ExecuteWithoutShimsとか、色々配慮されてて良い感じですねー。

ところでWebRequestは、IWebRequestCreateのStubを作ってWebRequest.RegisterPrefixにそれを登録するとWebRequest.Createは乗っ取ることが可能です、実は何気に。

var webreq = new StubIWebRequestCreate { CreateUri = uri => { /* hogemoge */ } };
WebRequest.RegisterPrefix("http://", webreq);

そして、これで実際WebClientのDownloadStringとかのWebRequest生成はフックできます。でも、これだと.NET 4.0から追加されたWebRequest.CreateHttpは乗っ取れないし、HttpClientにいたってはinternalなコンストラクタを使ってnew HttpWebRequestしているので、もはやそんな手法は実質完全無意味だ!ほんと、このあたりグダグダなので何も考えないほうがいいです。色々と幻想すぎる。

Shim vs Stub

vsというか、まずInterfaceはStubしか作れません。ある意味当たり前ですね。具象型は、Shimで作れば何でも差し替えられる、Stubで作るとvirtualなもののみ差し替えられる。具象型に関してはShimはStubの完全なる上位互換です。じゃあStub要らないのか、というと、割とそうでもなくて、Stubは軽量です。Shimは書き換えが入るので重たいです。このことはアプリケーションの設計全体に通しても言えて、Shimで何でも差し替えられるから、全面的にShimに頼ろう!みたいなのはダウトです。ダメ。それなりにテスタビリティを考慮した設計(= Stubで差し替え可能な状態)を作ったほうが良いです。

ただ、理想的な形がShimがゼロな状態でもテスタビリティ100%にすること、だとは私は思ってません。テスト可能にするために、ある程度、素直な設計を犠牲にして、歪んだ形になることって往々にあるはずです。そういうところは素直にShim使ったほうが100億倍良いでしょう。まあ、そのバランスに関しては答えなんてないので、各自で適宜、線を引いていくしかないかなーって思ってます。

あ、どうでもいいんですが、私はリポジトリパターンって嫌いで、いや、リポジトリパターンというか、ほぼほぼ100%テストのためだけにIHogeRepositryとHogeRepositryという実態作るとかIHogeとHogeImplが必ずといっていいほどセットなJxxxみたいじゃんというか、本当に嫌ですね!大嫌いですね!じゃあどうするかっつったら割とどうにもならないところもあるし、それをShimでサクッと殺すのがいいとは全然思いませんが、しかし私はShimで殺すことを選びますね。

まとめ

Fakes Frameworkは半端無く強力なので、とっとと全エディションに搭載されるといいなあ。Visual Studio 2012 SP1(いつ?)とかで、ね。いや、それじゃ遅すぎる、もっと早く!もっと早くに!Molesの頃はちょっと挙動に不安定さを感じた時もありましたが、さすがにプロダクト正式搭載なFakesは安定感もあってすっごくイイ。

ltxml.js - LINQ to XML for JavaScript

以前、linq.js ver.3のセッションの時に、ちょびっとだけ触れたLINQ to XML for JavaScriptが公開されました!

作者は私ではなく、Eric White氏です。誰?ってことですが、元Microsoftの人です。氏のMS時代のMSDNブログのLINQ to XMLタグには超DEEPなLINQ to XMLの使いこなしが書いてあって必見。というわけで、非常にLINQ to XMLに詳しい、というか、MSDNのLINQ to XMLの解説ドキュメントを書いたのは氏だそうです。なので、詳しいとかそういう次元じゃなく、普通に中の人ということですね。

概要

そんなわけで、ltxml.jsとは、C#のXML操作ライブラリであるLINQ to XMLのJavaScript移植です。C#のLINQ to XMLがLINQ to Objectsの上に構築されている(ElementsやDescendantsの戻り値がIEnumerableとなり、LINQベースのクエリ操作となる)ように、ltxml.jsはLINQ to ObjectsのJavaScript移植であるlinq.jsの上に構築されています。ltxml.jsのelementsやdescendantsは、linq.jsのwhereやselectなどのクエリ操作によってXMLを展開できます。

C#版と構造はほとんど一緒です。ただし、JavaScriptの慣習に則りメソッド名がlowerCamelCaseであることと、プロパティが.getHoge()であること(ただしECMAScript 5に対応しているならば.hogeといったようにプロパティでアクセスできます)、オペレーターオーバーロードが存在しないことによる挙動の違い程度です。また、C#版よりも機能向上している面もあります。それは、私がlinq.jsにおいてC#のLINQ to Objectsで物足りないと思った機能を追加していたようなもの、でしょうか、多分ね、きっと。

また、パフォーマンス上の考慮により、descendantsなどは、デフォルトは即時実行で配列(をEnumerableでラップしたもの)を返します。.lazy = trueをXElementなどに投下することで、完全な遅延実行になります。もし巨大なXMLを扱うのならば、遅延実行が効果を発揮するでしょう。通常考えられるサイズのXMLならば、デフォルトのとおり即時実行のほうが良好だと思われます。

使い方

ぶっきらぼうにも、ドキュメントがほとんどないですね!まあ、それは追々紹介されていくことでしょう。ともあれ現状は、ファイルをダウンロードするとltxmlTest.htmlというファイルがあって、それがユニットテスト兼サンプルになっているので、とりあえずそれを読みましょう。また、JavaScript特有の違いはあるものの、基本的にはC#のそれと等しいので、MSDNのLINQ to XMLの解説ドキュメントがまんま使えないこともないです。

ともあれ、まずは簡単なXMLをパースしてみましょう。

var xml =
    "<Contacts>\
        <Contact>\
            <Name>Peter Hage</Name>\
            <Phone>206-555-0144</Phone>\
        </Contact>\
        <Contact>\
            <Name>John Hoge</Name>\
            <Phone>106-333-2222</Phone>\
        </Contact>\
        </Contacts>";

// parseでただの文字列からLINQ to XMLのXElementに変換
var xElem = Ltxml.XElement.parse(xml);

// 子孫ノードのNameを選択し、値だけ抽出
var names = xElem.descendants("Name")
    .select(function (x) { return x.getValue(); })
    .toArray();

alert(names); // Peter Hage, John Hoge

descendants.selectといったように、LINQです!完全に!これをLINQと言わずして何をLINQと言うか!

名前空間

ltxml.jsの全てのクラスはLtxmlオブジェクトの下に格納されています。グローバルを汚さない。しかし、いちいちLtxml.XElementなどと呼ぶのは面倒くさい話です。以下のようなショートカットを先頭に用意するのをお薦めします。

var XAttribute = Ltxml.XAttribute;
var XCData = Ltxml.XCData;
var XComment = Ltxml.XComment;
var XContainer = Ltxml.XContainer;
var XDeclaration = Ltxml.XDeclaration;
var XDocument = Ltxml.XDocument;
var XElement = Ltxml.XElement;
var XName = Ltxml.XName;
var XNamespace = Ltxml.XNamespace;
var XNode = Ltxml.XNode;
var XObject = Ltxml.XObject;
var XProcessingInstruction = Ltxml.XProcessingInstruction;
var XText = Ltxml.XText;
var XEntity = Ltxml.XEntity;
var XEnumerable = Ltxml.XEnumerable;

また、C#版ではEnumerableへの拡張メソッドとして用意されていた幾つかのメソッドは、ltxml.jsではEnumerableに追加されているasXEnumerableを呼び、XEnumerableへと変換することで、呼び出すことができます。しかし、もしそれを手間だと思う場合は、linq.jsのEnumerableを拡張することで、よりスムーズに接続することが可能です。ただし、C#版ではジェネリックによって区別されていましたが、JavaScriptではジェネリックが存在しないので、汎用性のないシーケンスの要素がltxml.jsに固有でなければならないメソッドをEnumerableに追加することとなります。また、removeなどは、他の人の拡張と名前が衝突する可能性が高いことなどにも注意。

Enumerable.prototype.elements = Ltxml.XEnumerable.prototype.elements;
Enumerable.prototype.ancestors = Ltxml.XEnumerable.prototype.ancestors;
Enumerable.prototype.ancestorsAndSelf = Ltxml.XEnumerable.prototype.ancestorsAndSelf;
Enumerable.prototype.attributes = Ltxml.XEnumerable.prototype.attributes;
Enumerable.prototype.descendantNodes = Ltxml.XEnumerable.prototype.descendantNodes;
Enumerable.prototype.descendantNodesAndSelf = Ltxml.XEnumerable.prototype.descendantNodesAndSelf;
Enumerable.prototype.descendants = Ltxml.XEnumerable.prototype.descendants;
Enumerable.prototype.descendantsAndSelf = Ltxml.XEnumerable.prototype.descendantsAndSelf;
Enumerable.prototype.elements = Ltxml.XEnumerable.prototype.elements;
Enumerable.prototype.nodes = Ltxml.XEnumerable.prototype.nodes;
Enumerable.prototype.remove = Ltxml.XEnumerable.prototype.remove;

私個人としては、Enumerableへの拡張はそんなに薦められないかな、という感じですが、ヘヴィにXMLを処理する局面では、拡張したほうがサクサク書けて良いのではかとも思います。この辺は好みでどうぞ。

関数型構築

XMLを作るときは、コンストラクタで可変長引数として連鎖させます。これをLINQ to XMLでは関数型構築と呼んでいます。

var xml =
    new XElement("root",
        new XElement("user", new XAttribute("id", 1),
            new XElement("age", 100)));

// <root><user id = '1'><age>100</age></user></root>
alert(xml.toString()); // toStringで文字列化

閉じタグが不要であったり、安全であったり(JavaScriptだってカッコ閉じ忘れとかは警告入るからね)と、生文字列で組み立てるのに比べて、遥かに利点があります。また、要素がlinq.jsのEnumerableである場合も、きちんと展開されます。

// C#と同様にEnumerable<XElement>は展開される
var users = Enumerable.range(1, 10)
    .select(function (x) {
        return new XElement("user", new XAttribute("id", x),
                   new XElement("age", x * x));
    });

var xml = new XElement("root", users);

// <root>
//   <user id = '1'>
//     <age>1</age>
//   </user>
//   <user id = '2'>
//     <age>4</age>
//   </user>
//   (略)
// </root>
alert(xml.toString(true)); // 引数にtrueを渡すとインデントつけて整形

どうでしょう、イメージつきます?

用途

Eric White氏がOpenXML(Officeのxlsxとかがそれ)の専門家ということで、JavaScript上でOfficeファイルを容易に扱うことが可能になるでしょう。つまり、サーバーサイドでのコンバート処理など不要に、JavaScriptだけでビューワーであたり要素抽出であったりが、完結する未来があります。なんて興奮する話でしょう!

とはいえ、それはあまりにも専門的すぎて、実に面白いし役立つでしょうけれど、実際にそれでもって作り上げる側に周るのは極少数の人に違いありません。では、他にXMLを使う局面なんてあるのか?ふむ……。恐らく、ブラウザ上で動くアプリケーションにとって機会はないでしょう、どこもかしこもJSONに集約される!AJAXのXはXMLのX!だった時もありました。いや、今もそうでしょうけれど。

では、どこに?というと、Node.jsはどうだろう?結局、未だにXMLのサービスなどは多いのだ。RSSはJSONにならないでしょう。サーバーサイドで行うならXMLは無視できないのだ。またはクライアントアプリでも、TitaniumやPhoneGapはどうだ?またはWindows 8のアプリケーションはJavaScriptで作ることができる。そこではまだまだXMLは現役に違いない。JavaScriptの活躍範囲がブラウザを超えていけばいくほど、残り続けるXMLに遭遇する機会は増える。

AtomPub(ああ!今はもうあまり名前を聞かない!)の構築に、LINQ to XMLの関数型構築は役に立つことでしょう。とにかく言えることは、XMLは決して死んでいないし、望まれるか望まれないかはともかくとして、生き残り続けるでしょう。そのために、私達には強力な武器が必要なのです、LINQ to XMLという。

もしくは、単純にHTMLビルダーとして使ったっていいかもしれない。HTMLはXMLなのだもの、ええ、大丈夫、そのようにも使えます。文字列連結してHTMLを組み立てるなんてしなくていい。また、もちろん、HTMLのDOM操作にだって、ね(でもDOMの操作ならば、きっとjQuery使いますね)

未来

ところでしかし現状ltxml.jsはベータです。何故か。linq.jsがまだベータだからです。ver.3.0.3-Beta4ってなんだよクソが。すみませんすみません、なるべく早く正式リリース出来るよう鋭意努力します。NEETなので暇、じゃあないんですよねえ、残念ながら。でも急ぎます。さすがに!いやほんと私の作業ペースの遅さには我ながらどうかと思う感じですが、もうさすがに猶予ないですね!

ちなみに7月31日に作ってるよー、ってメール貰って、そこから何度かやり取りしてました。ltxml.jsのコード自体、かなりパワフルにlinq.jsを使いこなしているので(私のアドバイスの賜物です!)そういう意味でも面白いですね。ちなみに、その時には8月中にlinq.jsリリースするって言ってたかなあ、今もう10月末ですねえ、どうなってるんでしょうねえ、ごめんなさいごめんなさい。

既存JavaScriptをTypeScriptとして修正する方法

JavaScriptはTypeScriptです。ほぼほぼ。.jsを.tsとして変更すれば動きます。というほど世の中甘くなくて、まあ、大抵は動きません。えー、なにそれ、欠陥品じゃないの?と思われるかもですが、いえ、結構単純な話です。例えばですが

var x = 0;
x = "hogehoge";

このコード、JavaScriptとしては正しいですが、TypeScriptとしては間違っていてコンパイル通りません。xがnumberとして推論されるので、"hogehoge"が代入できないからです。じゃあどうするの?というと、

var x: any = 0;
x = "hogehoge";

anyとして型定義してやればいいんですね。もしくは

var x = <any>0;
x = "hogehoge";

でもいいですが。<>はキャストみたいなものです。ちなみに、こういったことの実例はTypeScriptのソースをダウンロードしてきて、\src\harness\external\json2.ts に、json2.jsをtsに変換した例が見れます。ほんの2, 3箇所anyの注釈を入れているだけで、ほぼほぼそのままですね。実際のところ、↑みたいなゆるふわキャストなんて、たとえJSといえど多用してるわけがないので、作業的な手間はあまりありません。やることは、コンパイルエラーの出た箇所をポチポチとモグラたたきするだけなので、楽ちん。

実際にやってみる

理屈上はそうですが、実際やってみるとどうなんでしょうねえ、ということで、linq.jsでやってみました。(なお、linq.jsの型定義自体はlinq.jsのTypeScript対応とTypeScript雑感で手付けですでにやってあります)。まず.tsにしてコンパイルかけてみると、赤波線が全体に出てきてわけわからんオワタ!エラー90件!

で、まあ、こういう場合は問題は基底部分にあります。

(function (root, undefined) {
// 中略
})(this);

問題なのはundefinedです。function(root, undefined) として定義しているのに、(this)ということで、呼んでないから。冷静に見てみれば、ただたんにメソッド呼ぶ引数が足りないぜ、って言ってるだけですな。ちなみにこのコード自体は、undefinedは代入可能な代物で破壊されている可能性があるから(あるわけないけど!)、安全なundefinedを作ろう、という古臭いイディオムです。

エラーが90件もあってわけわかりませんが、一番最初のエラーが「Supplied parameters do not match any signature of call target」なので、やっぱり冷静に見てみれば、ちゃんと教えてくれていた、と。TypeScript優しいのね。

なのでundefinedを抜けば真っ赤っ赤はなくなります。OK。だがまだエラーは続く。というかエラー件数は89件になっただけである。

お次はEnumeratorがないぞ!というエラー。

if (typeof Enumerator !== Types.Undefined) {
if (typeof Windows === Types.Object && typeof obj.first === Types.Function) {

このEnumeratorはIEのみに存在するオブジェクトで、Windows Script Hostで列挙するのに使ったり使わなかったりする、今では知らない人のほうが遥かに多いであろう謎オブジェクトです。Windowsのほうも同様に、Windows8用アプリケーションにしか存在しません。さて、これへの対処は、定義ファイルのない外部ライブラリを使う際と同じで、anyでdeclareします。ファイルの先頭に

declare var Enumerator;
declare var Windows

と書いておけばOK。しかしまだまだエラーは続くよ!該当箇所はここ。

var Enumerable = function (getEnumerator) {
    this.getEnumerator = getEnumerator;
};

// このUtilsで赤線
Enumerable.Utils = {}; // container

このEnumerableが意図するところはコンストラクタです。new Enumerable()するためのものです。で、JavaScriptでは関数にもオブジェクトを生やせますが、TypeScriptでは生やせません。対処方法はまあ、面倒くさいのでEnumerableをanyにしましょう。

var Enumerable: any = function (getEnumerator) {
    this.getEnumerator = getEnumerator;
};

これだけで割と一気に解決します!89件あったエラーが残りほんの数件に!any最強!dynamic! で、linq.jsでは同じようにOrderedEnumerableとArrayEnumerableというものが存在するので、同様にanyにしておきます。

そんなわけで、なんとなくわかったと思いますが、ようするにエラーの出てるところを片っ端からanyにしていくだけです。ただしルート階層に近いものを優先的にany化すると、その下にぶら下がってるものは全部解決するので、意外とそんな手間じゃありません。

あとは一番下にAMD対応もどきのとこがあるのですが、これはそもそも微妙なのでまるごと削除して解決(てきとー)。で、対応はほんとこれだけです。あっという間だし簡単ですなあ。TypeScriptのJavaScriptとの互換性は本物だ!

declarationsオプション

で、ここからが本題であって本題ではないのですが、TypeScriptはtsc -declarationsとオプションをつけてコンパイルすると、d.tsを吐いてくれます。ちゃんと型定義されたtsファイルならちゃんとしあd.tsを吐いてくれます。役立ちです。

で、人間欲が出るもので、もしこれを、↑のように修正した.tsにかませてやるとどうなる?もし、たとえanyであっても定義テンプレを吐いてくれたら、そこから注釈入れてくだけですむわけで、随分と楽になりますよね?面倒くさい型定義よさようなら。

というわけで、こいつをdeclarationsオプションをつけてコンパイルしましょう。

tsc linq.js.ts -declarations

期待のlinq.js.d.tsの結果は

var Enumerator;
var Windows;

になります(笑)。はい、関数で丸ごと括った部分が消滅してしまいました。クソが。今回は定義ファイルが欲しいだけなので、関数で括る部分を除去して再度コンパイルすると

var Enumerator;
var Windows;
var Functions: { Identity: (x: any) => any; True: () => bool; Blank: () => void; };
var Types: { Boolean: string; Number: string; String: string; Object: string; Undefined: string; Function: string; };
var Utils: { createLambda: (expression: any) => any; isIEnumerable: (obj: any) => bool; defineProperty: (target: any,methodName: any,value: any) => void; compare: (a: any,b: any) => number; dispose: (obj: any) => void; };
var State: { Before: number; Running: number; After: number; };
var IEnumerator: (initialize: any,tryGetNext: any,dispose: any) => void;
var Yielder: () => void;
var Enumerable: any;
var OrderedEnumerable: any;
var SortContext: any;
var DisposableEnumerable: (getEnumerator: any,dispose: any) => void;
var ArrayEnumerable: any;
var WhereEnumerable: (source: any,predicate: any) => void;
var WhereSelectEnumerable: (source: any,predicate: any,selector: any) => void;
var Dictionary;
var Lookup: (dictionary: any) => void;
var Grouping: (groupKey: any,elements: any) => void;

外に出したくないもの(Yielderとか)は、まあ、あとで別途削除すればいいんですが、しかしそもそも肝心のEnumerableメソッドが全部出てないぞ! 理由としては、ようするにanyつけちゃったから。うーん、これじゃ実用度ゼロですね。

そもそもfunctionで定義したクラス(をコンストラクタとして使いたい)というのを、それがコンストラクタなのか関数なのかをどうやって区別するんだ?って話ですし、無理ですなー。(ファイル全てをなめてthis.してるのはクラスとか.prototype = hogehogeしてるのはクラスとか、曖昧な判定はできるでしょうけれど、それは危険ですしね)。

夢は見ちゃダメ。でもMicrosoftならきっといつかはやってくれるはず!(実際、GWT用に空気読んでJavaScriptからJavaの定義を吐いてくれるものは存在するとか)

まとめ

プレーンなJavaScriptはほぼほぼTypeScriptになります。素晴らしい互換性です!一方、型定義自動生成のほうは無理でした。地道に頑張りましょう。

あ、そうそう、今回の記事で言いたいのは別に表題通りの話じゃあないです。既存JSは既存JSとして使えばいいので、わざわざTypeScript化する必要なんて全然ありません。いえ、あります、ちゃんと型付けするならば。でも、今回のようにanyを付けて回る程度の話は全くの無意味です。じゃあどうでもいいかといえば、やっぱりそうじゃなくて、TypeScriptとJavaScriptの互換性というのはどういうものなのか、というとこは位置づけ的には大事ですからね、漠然とじゃあなく抑えておきたいところ。

gloopsを退職しました。

今日というか昨日というか、金曜が最終出社日となりました。

今年の1月1日に入社してから10ヶ月。非常に濃密だったのであっという間でしたね。良い経験ができたし、私の方からも十分に貢献できたとは自負しています(LINQの布教とかね!)。とはいえ、まだまだやり残していることは山のようにあり、時期的にも、まさにこれから!というタイミングなので、心残りは非常にあります。gloops自体は非常に良い会社ですし、これからますます技術的にも力強く、面白くなっていくところです。なので、その点は安心してください。ですが、個人的により大きな飛躍を目指したく決断と相成りました。

円満、です。最後に社内勉強会でThe Patterns of LINQというセッションをやりまして、それが置き土産です。そんなこんなで温かく送っていただいて、本当にgloopsの皆様へ感謝!

ちなみに日本にずっといます。いつぞやかにはシンガポールが、とかって話もありましたが、あれは種々諸々で爆散しました。それは凄く残念でしたね……、、海外への挑戦というのは、またいつか機会があればやりたいです。ともあれ今は、当面は地下に潜伏していますが、必ず浮上しますのでしばしお待ちくだしあ。

非同期WebRequestとTimeout処理の今昔

最近はTypeScriptにお熱ですが、とはいえ、C#も大好きな私です。むしろC#は大好きです。今日はすっかり飽き飽きな非同期のTimeout処理について、おさらいすることにしましょう!題材はいつもどーりWebRequestでいいですよね。

まず、都合のいいTimeoutをシミュレートできるAPIは探せば多分あるでしょうが、面倒なので自分で作りましょう。いえ、簡単です。「空のASP.NET WebApplication」を立ち上げてジェネリックハンドラを追加。とりあえずレスポンスを返すのに3秒かかるということにしときましょう。

public class Timeout : HttpTaskAsyncHandler
{
    public override async Task ProcessRequestAsync(HttpContext context)
    {
        await Task.Delay(TimeSpan.FromSeconds(3)); // 3秒かかるってことにする
        context.Response.ContentType = "text/plain";
        context.Response.Write("Hello World");
    }
}

で、そのまま実行してIIS Expressで動いてもらってれば準備できあがり。

同期の場合

さて、そしてConsoleApplicationを立ちあげて、まずは同期でやる場合でも見ましょうか。

var req = WebRequest.Create("http://localhost:18018/Timeout.ashx");
req.Timeout = 1000; // 1秒でタイムアウト

req.GetResponse();

これはちゃんとタイムアウトでWebExceptionを返してくれます。そりゃそーだ。

古き良き非同期の場合

じゃあBegin-Endパターンの非同期でやってみましょうか。

var req = WebRequest.Create("http://localhost:18018/Timeout.ashx");
req.Timeout = 1000; // 1秒でタイムアウトのつもり

req.BeginGetResponse(ar =>
{
    var res = req.EndGetResponse(ar);
    Console.WriteLine(new StreamReader(res.GetResponseStream()).ReadLine());
}, null);

Console.ReadLine(); // 適当に待つ

この結果はなんと、普通にHello Worldと表示されてしまいます。はい、Timeout機能していません、全く。そう、WebRequestのTimeoutプロパティによる設定は同期限定なのだよ、なんだってー。このことはMSDNのBeginGetResponseのとこにも書いてあって、「非同期要求の場合は、クライアント側のアプリケーションが独自のタイムアウト機構を実装する必要があります」ということになっています。

ThreadPool.RegisterWaitForSingleObject

なので、そこに書いてあるとおり、ThreadPool.RegisterWaitForSingleObjectで実装してみましょう。

var req = WebRequest.Create("http://localhost:18018/Timeout.ashx");
// req.Timeout = 1000; このTimeoutはイミナイのでイラナイ

var result = req.BeginGetResponse(ar =>
{
    var res = req.EndGetResponse(ar);
    Console.WriteLine(new StreamReader(res.GetResponseStream()).ReadLine());
}, null);

ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, (state, timeout) =>
{
    // 引数で指定した時間の後にここの部分が発火する。
    // そのとき非同期処理が完了していなければ(Timeoutしていれば)timeoutがtrue, 普通に終了してればfalse
    if (timeout)
    {
        Console.WriteLine("TIMEOUT!!");

        var _req = (WebRequest)state;
        if (_req != null) _req.Abort(); // あぼーんでキャンセルというか打ち切る
    }
}, req, timeout: TimeSpan.FromSeconds(1), executeOnlyOnce: true);

Console.ReadLine(); // 適当に待つ

うん、ややこしいですね。ウンザリです。しかし昔はこれぐらいしか手段がなかったのだからShoganai!

C# 5.0で救われよう

そんなこんなで一気に時代が進んで、C# 5.0です。GetResponseAsyncですね!GetResponseAsyncなら、GetResponseAsync先生ならやってくれる、と思っていた時がありました。

static async Task Run()
{
    var req = WebRequest.Create("http://localhost:18018/Timeout.ashx");
    req.Timeout = 1000; // ま、このTimeoutはイミナイですよ

    var res = await req.GetResponseAsync();

    Console.WriteLine(new StreamReader(res.GetResponseStream()).ReadLine());
}

static void Main(string[] args)
{
    Run().Wait();
}

結果はBegin-Endの時と同じでTimeout指定は無視されます。はい残念残念。所詮別にBegin-Endと何も変わってはいないわけです。とはいえ、Taskならば、この辺、柔軟に処理を仕込めます。

Timeoutという拡張メソッドを作る

async/awaitやTaskといった道具立てはあるのですが、細かい色々なものはない(Cancelを足すとかTimeoutを足すとかRetryを足すとか、この辺はよく使うであろうシチュエーションだと思うので、自分の道具箱に仕込んでおくと幸せになれます)ので、作りましょう。

public static async Task Timeout(this Task task, TimeSpan timeout)
{
    var delay = Task.Delay(timeout);
    if (await Task.WhenAny(task, delay) == delay)
    {
        throw new TimeoutException();
    }
}

public static async Task<T> Timeout<T>(this Task<T> task, TimeSpan timeout)
{
    await ((Task)task).Timeout(timeout);
    return await task;
}

単純ですね。ポイントはTask.WhenAnyで、これは特殊なやり方ではなくて、イディオムです。C# 5.0を使っていくなら覚えておきましょう、絶対に。

さて、これを使えば

static async Task Run()
{
    var req = WebRequest.Create("http://localhost:18018/Timeout.ashx");
    var res = await req.GetResponseAsync().Timeout(TimeSpan.FromSeconds(1));

    Console.WriteLine(new StreamReader(res.GetResponseStream()).ReadLine());
}

static void Main(string[] args)
{
    Run().Wait();
}

超シンプルになりました。やったね!

HttpClient

ちなみに.NET 4.5から入ったHttpClientは、非同期操作しか提供していない、だけに、ちゃんとTimeoutプロパティが非同期でも対応していますので、フツーはこっちを使うと良いでしょう。

static async Task Run()
{
    var client = new System.Net.Http.HttpClient() { Timeout = TimeSpan.FromSeconds(1) };
    var s = await client.GetStringAsync("http://localhost:18018/Timeout.ashx");

    Console.WriteLine(s);
}

static void Main(string[] args)
{
    Run().Wait();
}

吐いてくる例外はSystem.Threading.Tasks.TaskCanceledExceptionです。これは、中でCancellationTokenSource.CreateLinkedTokenSourceとリンクさせた上で、CancellationTokenSourceのCancelAfterによってTimeoutを処理しているからです。HttpClientも、最終的にネットワークとやり取りしている部分はWebRequestですから。

まとめ

まあ、今までは細かい罠があってクソが、となる局面も少なからず多かったわけですが、ようやく整理された、感があります。しょっぱいことは考えないで、甘受していきたいですねー。

linq.jsのTypeScript対応とTypeScript雑感

MicrosoftからTypeScriptという新言語が発表されました。驚くべきは、あのC#のAnders Hejlsbergが関わっている!これはもう触るしかない。そしてこれはコンパイル後にJavaScriptになる言語(CoffeeとかJSXとかみたいな)なわけで、じゃあlinq.jsを対応させるしかない!というわけで、させました。

// TypeScript
Enumerable.range(1, 10)
    .where(x => x % 2 == 0)
    .select(x => x * x)
    .writeLine();

// コンパイル後
Enumerable.range(1, 10).where(function (x) {
    return x % 2 == 0;
}).select(function (x) {
    return x * x;
}).writeLine();

ひゃっはー、もうfunction() { return }とはオサラバだ!そしてこの記述性と最強のコレクション操作であるLINQが合わさると最強に見える。

に型定義ファイルは同梱してありますので、是非是非お試しを。NuGetのlinq.js -Preでも入ります。NPMは予定はありますが、まだです、すみません。

TypeScriptについて

型安全なCoffee Script、といった印象ですね。基本的にはCoffee Scriptに近いと思います。JavaScriptにプラスアルファな構文を採用することで、既存のJSライブラリとの繋がりを良くすることと、綺麗なJavaScriptを吐くことに重きが置かれている。TypeScriptは、比較的素直にJavaScriptに読み解くことが出来て、独自のコード生成は、現状はほぼほぼ無いし、意図的に省かれているようです(例えば非同期にたいしてasync構文を入れたりすると、大量のコード生成が入り、出力されるJavaScriptが機械的に汚れがち)。

そういった点、機能面では、TypeScriptには物足りなさを感じるところが多いかもしれません。じゃあJavaScriptに対する強みってどこなんだよ!といったら、一つはJavaScriptの冗長な記述性の補正(class,module, arrow function, Object.create/definePropertyとかも最低だしね)。もう一つは、無理なく自然に馴染んだ型付け。

型はないよりあったほうがいい。でも、型を付けるのがあまりにも苦痛だったら?ちょっとしたコードを書くのにも型!型!型!と押し付けられたら?そりゃあ、嫌だ。というわけで、型推論によって、比較的スムースに書けるようになっています。

型推論の性質というか範囲というか強さというかは、C#と非常に近いというかC#とまるで一緒なので、C#erならサクッと馴染めます。もっと強力な型推論のある言語に馴染んでいる人だと、え、ここで効かないの?みたいな違和感はあるかもですが。

また、さすがはMicrosoftというかAnders Hejlsbergというか、入力補完のことを念頭に置いた言語設計になっているので、IDEとの相性が非常に良い。そして最初からVisual StudioによるIDE環境が用意されていることで、型のある利点の一つであるリアルタイムエラー通知や入力補完をたっぷり満喫できます。さらに、それはTypeScript PlaygroundによってWeb上でも体感できます。というか、もはやPlaygroundはWeb IDEとでも言うべき驚異的な動き!

また、Windowsだけではなく、最初からSublime, Vim, Emacsの対応ファイルが用意されているというところからも、Windowsに限らず幅広く請求したい、という表れだと思います。そして実際、言語はプラットフォーム中立なわけです(最終的にはJavaScriptだしね!)。

Structural Subtyping

TypeScriptの最も面白いところは、ここです。C#とかのインターフェイスとLLのダックタイピングの中間、みたいな。実にゆるふわなJavaScriptと絶妙に合っててイイ!というかそもそも私はStructural Subtypingって名前だけでぜーんぜん分かってなかったのですが、TypeScriptだと自然と馴染めます。ほむ、どういうことか、というと、一例を。

union(second: any[], compareSelector?: (element: any) => any): Enumerable;
union(second: Enumerable, compareSelector?: (element: any) => any): Enumerable;
union(second: { length: number;[index: number]: any; }, compareSelector?: (element: any) => any): Enumerable;

これはlinq.jsの型定義の一つでunion、和集合を生成するためのメソッドです。なので、元シーケンスと、対象シーケンス(second)が対象になるわけですが、じゃあシーケンスって何?と。列挙できればいいので、配列もそうだし、Enumerableもそう。そして、JavaScriptに特有の存在として、配列みたいだけど配列じゃあないもの(lengthを持っていてインデクサでアクセスできる)、例えばDOMのNodeListとか、もそう。

で、そういった「lengthを持っていてインデクサでアクセスできる」という型の定義が{ length: number;[index: number]: any; }。これにより、DOMのNodeListやjQuery([0]とかでHTMLElementが取れる)など、配列みたいだけど配列じゃないもの全てが型安全に定義されました。やったね!

もしC#だったら、対象はインターフェイスで指定するしかないので、IEnumerable<T>を実装していないクソコレクションクラスが存在したら、それは列挙不能になってしまいます。片っ端からオーバーロードを作るのは不可能だし、かといってdynamic secondなどとしてしまってはアレ。

とはいえ、基本的にC#では最初から全てのシーケンスはIEnumerable<T>を実装している、という前提が成り立っているので、問題はおこらない。でも、JavaScriptは違う。配列みたいだけど配列じゃあないもの、が跋扈してる。でも、そこをanyとして何でも受け入れられるようにしたら型安全じゃあない。安全にしたい。そこをStructural Subtypingが華麗に解決してくれました!惚れた……。

TypeScriptはJavaScriptか?

JavaScriptコードはそのままTypeScriptだ!ということにYesと言えるかというと、イエスでもあり、しかし割とノーです。私がこの話を聞いて、最初に思ったのは、既存JSコード、つまりライブラリ類もそのままで動くのかな?と。答えはNOです。JS固有の、実行時に切った貼ったして構造作っていくの、ああいうのTypeScriptだと軒並みコンパイルエラーになるので、ダメです。ほとんどのライブラリが絶滅でしょう。

と、勘違いしていたのですが(yharaさん指摘ありがとうございます!)

declare var $; // jQuery
declare var _; // underscore
declare var Enumerable; // linq.js

とかって定義すると、これはそれぞれ $:any, _:any, Enumerable:any という扱いになって、以降はどんなチェーンを繋げてもエラーが起こらない、つまりライブラリが正常に読み込めたかのようになります。

ただ、型チェックや入力補完が効かなくなるので、TypeScript用の型注釈ファイルはあったほうがいいですね。有名ライブラリはともあれ、無名プラグインとかは自前で型注釈書かなければならないかもり。手書きだとかったるいので、自動生成である程度テンプレート吐き出してくれないと、面倒くさい。この辺はMicrosoftだしやってくれるんじゃないかなあ、という淡い期待を抱いていますが……。

とはいえ、ちょっとしたコンパクトなプラグインを使ったり、ライブラリ使うとしても一部分だけだしー、などというのに、わざわざ型定義も馬鹿らしいわけで、さくっと動的な感じにdeclareできちゃう、というのは素晴らしい話。

そんなわけで、JavaScript→TypeScriptの相互運用性としては、繋がりはかなり良好。勿論、jQueryなどもスムースに扱うことができます。これは、文法がJavaScriptプラスアルファで構築されているがことの利点です。そしてTypeScript→JavaScriptは、というと、素直なJavaScriptを吐いてくれることもあり、良好です。TypeScriptで作られた生成物は、TypeScriptだけに閉じません。

JavaScriptを中間言語とする選択肢が増えた。JavaScriptを介することで他の言語とも自由に繋がる。ここには、Webの互換性、中立性を崩す要素は一切ありません。独自言語による囲い込みとかではありません。素直に歓迎できるはずです。ただし、言語としてはあくまでTypeScriptはTypeScriptです。そこだけは、誤解しないほうがいいと思います。文法的に、ES6を若干取り入れているとはいえ、違う言語です。将来的にもTypeScriptはEcmaScriptにならないでしょうし、EcmaScriptはTypeScriptにはならないでしょう。TypeScriptはEcmaScript6のただの代替なのではない、別の価値ある言語です。

変な期待をして、これJavaScriptじゃないじゃん、とかって難癖つけたりは、あまり良くないですね。

TypeScriptとVisual Studio

別にMicrosoft環境に閉じる言語ではないので、EmacsでもVimでもいいですが、やはりVisual Studioが第一な点は少なからずあります。LinuxでもIDEで書きたい?きっとJetBrainsがWebStormに搭載してくれるはずです!(実際、Voteはかなり集まってました)

ともあれ、Visual Studioです。専用拡張のインストールはTypeScriptのサイトから出来ます。プロジェクトテンプレートが何故かC#のところにあって気づきにくいことに注意!それともう一つ、Web Essentialsを入れましょう。元々Coffee ScriptとLESSに対応していたのですが、今回TypeScriptにも対応してくれました。Web Essentialsを入れることで、保存時のコンパイル(通常の拡張だとビルド時のみ)と、ウィンドウ分割での出力後のJS表示、それとSourceMapファイルの出力を行ってくれます。

勿論、IntelliSenseはフルに効くしエラーはリアルタイムでがんがん通知してくれます。TypeScript Playgroundと違うのは、エラーがあるとJSに変換してくれないところですね。まあ、それは正しい挙動なのでいいです。Playgroundで中途半端なエラーのある状態でもガンガン変更表示してくれるのは、それはそれで便利なので、それもまたいいです。

ちなみに、TypeScript Playgroundでは赤波線が出ている状態は、一応、JSを出力してくれてますが、それはコンパイルエラーの状態で完全な出力がされていないと思って良いです。つまり、本来的には動いてないわけです。この動いていない出力を指して、(現状Firefoxにしか乗ってない)JavaScriptへの互換が不完全とかって難癖つけたりするのは、ほんと良くないですね……。

SourceMap

Web Essentialsの吐いてくれるSourceMapとは何ぞや、というと、これはTypeScriptのままデバッグができます。コンパイル時にJSを吐いてくれる系言語の欠点として、デバッガを使ったデバッグが困難、というのが挙げられますがSourceMapを使うとそれも解決、します。

現状、対応ブラウザはChromeと、まあ、他は知らないのですが、とりあえずChromeは対応しています。IE10(とVS2012内蔵デバッガ)も対応してくれると嬉しいなあ。Chromeのデバッガの不満点としては、ブレークポイントが行単位でしか貼れないことですね。ラムダ式の内側に貼れないと、特にLINQのような一行ラムダを多用するものではデバッグがとても不便でして。この辺、改善されていってくれると嬉しい話。

vs JavaScript(のIntelliSense)

実は、VisualStudio 2012のJavaScriptはかなりサポートが手厚く、裏で常にコードを実行して補完候補を出してくれたりします。

なので、純粋なIntelliSenseの効きだけでいうと、TypeScriptはJavaScriptに負けているかもしれない!如何せん、特にlinq.jsではシーケンスの要素がanyになってしまいますからね。JavaScript(を裏で動かして解釈する)ならば、ここも補完効いてしまうという。最近のJavaScript IDEは進化しすぎで恐ろしい……。

ジェネリクス

仕様書にも明言されていますが、正式リリースまでには搭載する予定があるそうです(ちなみに現在は0.8)。ジェネリクスが乗っかるとlinq.jsがすっごくパワフルになるんですよ。如何せん、今はシーケンスの要素の型が全てany扱いで補完が全く効かなくてTypeSafeでもなんでもないのですが、ここが型付けされると完璧なIntelliSense生活!C#並というかむしろC#超えるぐらいの勢いでパーフェクトなLINQ to Objects!なので、相当に待ち遠しいです。

Compiler as a Serviceの未来

TypeScriptのコンパイラはTypeScriptで書かれてます。これ、別にかっこつけとかでもなんでもなく、非常に重要な意味を持ちます。で、いきなり分かりやすく成果物として出してくれているのがTypeScript Playground。構文解析がJavaScriptで可能だから、Web上で全て完結するIDEが作れる。C#も次のバージョンではC#コンパイラがC#で書かれるという計画があります。そのことがもたらす価値の一部分は、TypeScriptが教えてくれます。いや、むしろブラウザ上で全て完結というのは、C#以上の魅力がありますね、正直……。

結論

TypeScriptは、良い言語だと本当に本当に思います。私は、素のJavaScriptも別にそこまで嫌いではないのですけれど、やっぱ、違うなあ、と。なので今後は積極的に使っていきたいところです(CSSもLESSで!)。

言語設計者が同じということもありますが、特にC#erには絶対馴染むと思うので、(linq.jsとセットで)今までJavaScriptとは無縁だった人も手を出して欲しいですね。きっと気に入りますし、視点が変わります。勿論、ネイティブJSerも是非是非触ってみるといいと思います!というか触ってほしいです。

あ、あと、軽く流しましたがVisual StudioユーザーならWeb Essentialsも必ず入れておきましょう。これがあるのとないのとでは、TypeScriptの使い勝手全然違ってくるので、TypeScript試すならば必須です。

linq.js ver.3.0.2-RC, WinRT対応、最新RxJS対応など

RCリリースしました!これでAPI弄りは終了で、あとはドキュメント周りの調整のみといったところです。

ダウンロードはダウンロードボタンからではなく、ダウンロードタブからlinq.js ver.3.0.2-RCを選択してください。というかここからダイレクトに飛んでください

Beta2から結構立ちましたが、その間に、ノートPCがぶっ壊れたり(今もサポートで修理中、ちょうどうっかり未Pushなコミットが溜まってた状態で逝ってしまったのが痛手でどぅーにもこーにも)、そもそも日本にいなかったり(シンガポールにいましたというか、今は日本にいますが、これからは基本的にシンガポール中心になる感)とか、まぁ色々で色々で捗らずで。

さて、その間でもないですが、プログラミング生放送で8/25にセッションを持ちまして、そこでlinq.js ver.3の紹介をしましたので、スライド・録画ともどもにどうぞ。

linq.js ver.3 and JavaScript in Visual Studio 2012 from neuecc

Visual Studio 2012はJavaScript関係がハイパー強化されているのですけれど、そのビッグウェーブにフルに乗っかって強力なんだぞ!みたいな。そういったVS2012のパワーとかの部分は、デモの比率が高いので、時間に余裕があれば、是非とも録画を見ていただけると嬉しいです。

あと、こそっとLINQ to XMLのアナウンスを、こそっと、ね。ふふり。

あ、そうだ、スライドに関しては一点、嘘があります。VS2012にはjQueryのIntelliSenseドキュメントの日本語版は入っていません。英語のみです。本当にごめんなさい、これ、確認していなくて、VS2010では日本語訳されたのが入ってたからそうなんだろうなあ、とか思ってたのですが、そんなことはなかったです。予算的な都合なのでしょうか……?ともあれ、申し訳ありませんでした。

更新事項

今回も破壊的変更が入っていて、firstOrDefault, lastOrDefault, singleOrDefaultの引数が変わりました。

// 以前
.firstOrDefault(defaultValue, [predicate]);

// これから
.firstOrDefault([predicate], [defaultValue]);

です。ようするに引数が逆になりました。predicateが先にあるのがC#のLINQと同じ並び順なわけで、ここだけ、何故かlinq.jsはC#に従ってなかったのですね。理由としてはJavaScriptにはdefault(T)は存在しないのでdefaultValueの指定を強制するために、第一引数に持ってきてやらなければならない。と、当時、3年前は思ってたらしーんですが、別に普通にないならないでnullでいいだろ馬鹿が、むしろ引数がこれだけ違うとか紛らわしいだろクソが。ということにやっと至りまして、変えられるのは今しかない!ということで変えました。

コードスニペット

そういえば3.0.1-Beta2のリリース時にはブログ書いてませんでしたが、そこでコードスニペットを改良したのを入れました。linq.js ver.2に同梱してたものよりずっと良くなってるのでリプレースするといいです。非常に捗ります。というか、もうこれなしで書く気しないぐらいに。

RxJS

Reactive Extensions for JavaScript(RxJS)がオープンソースになりました、完全にソースコード公開です、ぱちぱちぱち。今まではScriptSharpで変換してたような気配だったのですが、完全手書きに移行したようです。

それに伴ってlinq.jsのRxJS連携も、若干手を加えました。ところで、今のところNuGetにあがっているものは、GitHubの最新に追随してません。古いままです。なので、NuGet版だとlinq.jsのRxJS連携は動かなかったりします(ビミョーに中身変わってるんですよ、いやはや……)

今のところ新RxJSに関してはリリースとかも打たれていないので、ステータスがどうなっているのか、よくわかりません。まあ、近日中に、かなあ?どうなのでしょうね。とりあえず、動向には注目、ということで。

WinMD

Windows 8のアプリケーション(Metroとは言えなくなりました!)はJavaScriptでも開発できるわけでして&C++やC#で作成されたライブラリも条件付きというか専用のコンポーネントとして作れば、JavaScriptでも読み込むことができます。 コレクション周り、IList<T>はJavaScriptでは配列として扱えます。なので、これは何もなく普通に列挙できるし、今までのlinq.jsでも扱うことができました。しかし、IEnumerable<T>はIIterable<T>というものに化け、これは独特の列挙の仕方を要求するため、フツーには扱いづらく、また、今までのlinq.jsでも使えませんでした。

が、ver.3.0.2-RCからは、IIterable<T>対応を入れたので、列挙可能です!

// IIterable<T>を列挙できるのはlinq.jsだけ!
var iterable = ToaruLib.GetIterable();
Enumerable.from(iterable).forEach();

WSH(JScript)対応といい、Windows固有のニッチ需要に100%応えるのはlinq.jsだけ。いやほんと。

文字列ラムダと無名関数

これは今までもの話なのですが、文字列ラムダに抵抗あるー、というのは分かります。しかし、無名関数を渡すこともできますぜ、というのは、分かって欲しいというか、利用シーンとしては半々なイメージなんですよね。例えばですが二つのJSONをJoinするのに

var jsArrayA = [{ "projectid": 122, "projecttype": "radio" },{ "projectid": 133, "projecttype": "tv" }];

var jsArrayB = [ { "actionid": 1, "name": "kuow", "pid": 122 }, { "actionid": 2, "name": "kplu", "pid": 122 }, { "actionid": 3, "name": "abc", "pid": 133 }, { "actionid": 4, "name": "espn", "pid": 133 } ];

var queryResult = Enumerable.from(jsArrayA)
    .join(jsArrayB, "$.projectid", "$.pid", function (a, b) {
        return {
            projectid: a.projectid,
            projecttype: a.projecttype,
            actionid: b.actionid,
            name: b.name,
            pid: b.pid
        }
    })
    .toArray();

これ、全部、無名関数で書くことも可能です。"$.projectid"をfunction(x){ return x.projectid} と書けばいいので。"$.pid"の部分もそうです。でも、それってすごくだるいですよね。

LINQはプロパティ名を指定するだけの無名関数を要求するシーンが多いです。どうせ、JavaScriptは動的言語、コンパイルチェックも働かないのですから、文字列で指定しても一緒でしょう。また、これは、jQueryのセレクターと同じようなものです。そう考えれば、文字列指定にもさして抵抗感はないのではないでしょうか?短くサラッと文字列でプロパティ名を指定したほうが、書きやすいし可読性も高いです。

同様に、最後のJOIN結果を新しいオブジェクトに変換しているところは、文字列ラムダで書くことも可能です。"{projectid:$.projectid, projecttype:$.projecttype,....}"といったように。でも、それって今度は逆にとても見づらくて可読性落ちますよね。長いコード、入り組んだコードになるようならば、素直に無名関数を使ってもらうのがいいな、と思っています。

次回

次は正式リリースです!いつになるかは、いつになるかしらん!8月末が正式リリースのつもりだったのに、一か月遅れでRCリリースですからねえ、んもぅー。ともあれ、間違いなく良い出来になっているので、楽しみにしてください。で、もうその前にRCじゃんじゃん使ってくだしあ。

複数の値とC# 5.0 Async再び、或いはAsyncEnumerableへの渇望とRx

以前にReactive Extensions + asyncによるC#5.0の非同期処理では、単体の値であったらasync、複数の値であったらIObservable<T>が使い分け、とかかんとかと言ってましたが、本当にそうなの?もしくは、そもそも複数の値のシチュエーションって分かるような分からないようななのだけど?などなどと思ったりする昨今を如何様にお過ごしでしょうか。というわけで、今回はグッとこの部分に深く迫ってみましょう。

同期的なシチュエーション

さて、例、なのですけれど、データベースで行きましょう。生DataReaderを転がしてます。

// 接続文字列に Asynchronous Processing=true は非同期でやるなら欠かさずに
const string ConnectionString = @"Data Source=.;Initial Catalog=AdventureWorks2012;Integrated Security=True;Asynchronous Processing=true;";

// こういうreader.Readが尽きるまで列挙するだけのヘルパーがあるだけで
static IEnumerable<IDataRecord> EachReader(IDbConnection connection, string query)
{
    using (var command = connection.CreateCommand())
    {
        command.CommandText = query;
        if (connection.State != ConnectionState.Open) connection.Open();

        using (var reader = command.ExecuteReader())
        {
            while (!reader.IsClosed && reader.Read()) yield return reader;
        }
    }
}

static void Main(string[] args)
{
    using (var conn = new SqlConnection(ConnectionString))
    {
        // LINQでSelectとかいろいろ出来る!便利!抱いて!
        var result = EachReader(conn, "select * from Sales.Customer")
            .Select(x => new
            {
                CustomerID = x.GetInt32(0),
                PersonID = !x.IsDBNull(1) ? (int?)x.GetValue(1) : null,
                StoreID = !x.IsDBNull(2) ? (int?)x.GetValue(2) : null,
                TerritoryID = !x.IsDBNull(3) ? (int?)x.GetValue(3) : null,
                AccountNumber = !x.IsDBNull(4) ? x.GetString(4) : null
            })
            .ToArray();

        // PKでとりたければFirstOrDefualtとかでいいわけです
        var customer = EachReader(conn, "select * from Sales.Customer where CustomerID = 100")
            .Select(x => new
            {
                CustomerID = x.GetInt32(0),
                PersonID = !x.IsDBNull(1) ? (int?)x.GetValue(1) : null,
                StoreID = !x.IsDBNull(2) ? (int?)x.GetValue(2) : null,
                TerritoryID = !x.IsDBNull(3) ? (int?)x.GetValue(3) : null,
                AccountNumber = !x.IsDBNull(4) ? x.GetString(4) : null
            })
            .FirstOrDefault();
    }
}

ExecuteReaderの結果のIDataRederは、yield returnで列挙してやると、LINQで加工できるようになるので、SelectしてToArrayとか、SelectしてFirstOrDefaultとか、非常にやりやすくて便利なわけです。

ここまでは、いいと思います。じゃあ、非同期でやると、どうするの、と。

内部イテレータ的に考える

.NET Framework 4.5からは主要な非同期メソッドに全てXxxAsyncという名前のものがつきました。ADO.NETにおいては、OpenAsyncやExecuteReaderAyncなどがあります。というわけで、試してみましょう。

// 非同期だったこうしたい、でもこれはコンパイル通らない!
static async Task<IEnumerable<DbDataReader>> EachReaderAsync(DbConnection connection, string query)
{
    using (var command = connection.CreateCommand())
    {
        command.CommandText = query;
        if (connection.State != ConnectionState.Open) await connection.OpenAsync();

        using (var reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess)) // 基本的にはSequentialAccessにしておきたい
        {
            while (!reader.IsClosed && reader.Read()) yield return reader;
        }
    }
}

残念ながら、asyncとyield returnを共存させることはできないので、どうにもなりません。……はい。しかし出来ない、では困る。さすがに、非同期実行するところに全部生のままで、こんなCreateCommand..., ExecuteReaderAsync, .... なんて書いてられないし。

じゃあどうするか、というと、内部イテレータ的にしましょう。つまりList<T>にあるようなForEachです。yield returnが外部イテレータ的であり、それが無理なら、内部イテレータ的にすればいいぢゃない。

// ループを回してる最中に実行するaction引数をつけた(FuncじゃなくてAction<DbDataReader>のオーバーロードも作るとベター)
static async Task ForEachAsync(DbConnection connection, string query, Func<DbDataReader, Task> action)
{
    using (var command = connection.CreateCommand())
    {
        command.CommandText = query;
        if (connection.State != ConnectionState.Open) await connection.OpenAsync();

        using (var reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess)) // 基本的にはSequentialAccessにしておきたい
        {
            while (!reader.IsClosed && reader.Read()) await action(reader);
        }
    }
}

static async Task<List<Customer>> GetCustomers()
{
    using (var conn = new SqlConnection(ConnectionString))
    {
        // これで、Selectっぽくできてないこともないと言えなくもない
        var list = new List<Customer>();

        await ForEachAsync(conn, "select * from Sales.Customer", async x =>
        {
            var customer = new Customer
            {
                CustomerID = await x.GetFieldValueAsync<int>(0),
                PersonID = !x.IsDBNull(1) ? await x.GetFieldValueAsync<int?>(1) : null,
                StoreID = !x.IsDBNull(2) ? await x.GetFieldValueAsync<int?>(2) : null,
                TerritoryID = !x.IsDBNull(3) ? await x.GetFieldValueAsync<int?>(3) : null,
                AccountNumber = !x.IsDBNull(4) ? await x.GetFieldValueAsync<string>(4) : null
            };
            list.Add(customer);
        });

        return list;
    }
}

// LINQじゃないので匿名型は使えないのね
public class Customer
{
    public int CustomerID { get; set; }
    public int? PersonID { get; set; }
    public int? StoreID { get; set; }
    public int? TerritoryID { get; set; }
    public string AccountNumber { get; set; }
}

ForEachAsync!というわけで、Actionを渡してやって、そこでグルグルッとすることにより制限を回避もどき。ToArrayしたい?古き良きListにAddすればいいぢゃない、といったものですよ、ははは。LINQじゃないので匿名型は使えないがね!

さて、匿名型が使えないのはいいとしても、問題は、LINQの特徴である合成可能性を欠いているところです。一番困るのは、これ、FirstOrDefaultできないね、って。全件取るんですか?まあ、PKだったら一件なのが保証されてるし、そうでないならtop 1とでも書いておけよ、と言えなくもないですが、しかしどうなのよこれ、と。

そんなわけでForEachAsyncと、もう一つ、ExecuteSingleAsyncという名前で、一件のみを列挙するようなものを別途作る必要があります。とはいえ、それでも対応できているのは一件と全件だけ。例えばTakeWhileみたいなのがやりたい、SkipWhileみたいなのがやりたいとなったらどうするの、と。答えは、どうにもなりません。諦めるしかない。

AsyncEnumerableで救われる

どうしても諦められないのならば、AsyncEnumerableを授けましょう。NuGetからIx_Experimental-Asyncを引っ張ってきます。Ix、そう、みんな大好きReactive Extensionsの兄弟なわけですが、しかし紹介しておいてアレですが、このIx_Experimental-Asyncはお薦めはしません!完全に実験的に、できるから、というだけで実装例を掲示してみせてくれたというだけなノリがぷんぷんしているからです。実際、最初のAsync CTPが出た時に公開されて、それから更新されてませんしね……。

ともあれ、どんなコンセプトの代物なのかは見ておきましょう。

// こういうDB列挙用のIAsyncEnumerable/Enumeratorを作る。
// yield returnのようなコンパイラサポートはないので手書きするんだよ!
public class AsyncDbEnumerable : IAsyncEnumerable<DbDataReader>, IAsyncEnumerator<DbDataReader>
{
    DbConnection connection;
    DbCommand command;
    DbDataReader reader;
    string query;

    public AsyncDbEnumerable(DbConnection connection, string query)
    {
        this.connection = connection;
        this.query = query;
    }

    public IAsyncEnumerator<DbDataReader> GetEnumerator()
    {
        return this;
    }

    public DbDataReader Current
    {
        get { return reader; }
    }

    public async Task<bool> MoveNext(System.Threading.CancellationToken cancellationToken)
    {
        if (command == null)
        {
            if (connection.State != ConnectionState.Open) await connection.OpenAsync();

            command = connection.CreateCommand();
            command.CommandText = query;
            reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess);
        }

        if (await reader.ReadAsync())
        {
            return true;
        }
        return false;
    }

    public void Dispose()
    {
        reader.Dispose();
        command.Dispose();
    }
}

static IAsyncEnumerable<DbDataReader> EachReaderAsync(SqlConnection connection, string query)
{
    return new AsyncDbEnumerable(connection, query);
}

static async Task Test()
{
    using (var conn = new SqlConnection(ConnectionString))
    {
        // 完全にLINQなのでSelectしてToArrayで匿名型もOK
        var result = await EachReaderAsync(new SqlConnection(ConnectionString), "select * from Sales.Customer")
            .Select(x => new
            {
                CustomerID = x.GetFieldValue<int>(0),
                PersonID = !x.IsDBNull(1) ? x.GetFieldValue<int?>(1) : null,
                StoreID = !x.IsDBNull(2) ? x.GetFieldValue<int?>(2) : null,
                TerritoryID = !x.IsDBNull(3) ? x.GetFieldValue<int?>(3) : null,
                AccountNumber = !x.IsDBNull(4) ? x.GetFieldValue<string>(4) : null
            })
            .ToArray();

        // 勿論FirstOrDefaultもできる
        var customer = await EachReaderAsync(new SqlConnection(ConnectionString), "select * from Sales.Customer where CustomerID = 100")
            .Select(x => new
            {
                CustomerID = x.GetFieldValue<int>(0),
                PersonID = !x.IsDBNull(1) ? x.GetFieldValue<int?>(1) : null,
                StoreID = !x.IsDBNull(2) ? x.GetFieldValue<int?>(2) : null,
                TerritoryID = !x.IsDBNull(3) ? x.GetFieldValue<int?>(3) : null,
                AccountNumber = !x.IsDBNull(4) ? x.GetFieldValue<string>(4) : null
            })
            .FirstOrDefault();
    }
}

IAsyncEnumerableの実装は完全に手作りです!真面目に使うなら、AnonymousAsyncEnumerableとかを作って、それを使って構築しますが、今回はてきとーな感じに実装しておきました。てきとーと言っても、ちゃんと動きますよ、はい。

さて、これによってIAsyncEnumerableに変換されたシロモノは、LINQのメソッドが全て使えます。おー、やったね、これなら完璧。

と、言いたいのですが、よーくSelectの中のラムダ式を見ると、ForEachAsyncの時のものと違うのが分かるでしょうか?ForEachAsyncの時はGetFieldValueAsyncといった、値取得まで非同期のものを使いました。でも、今回はそれは使ってない。何故かというと、そこでasyncにしてしまうと戻り値がIAsyncEnumerable<Task<T>>になってしまうから。

何がいけないのか、というと、例えばToArrayした結果で見るとresult[0]はTaskなんですよ。実態はresult[0].Resultとしなきゃあいけません。おまけに、全部Taskということは、実行中かもしれないわけです。じゃあ全部待てばいいのか、 await Task.WhenAll(result) とすればいいか、というと、そうなると、一つのコネクションで複数実行が走る、この場合Connectionは複数実行は許容されていないので、まあ例外が飛んできてしまうでしょう。

じゃあasyncは諦めるの?というと、幾つか手はある。ひとつはAwaitとかいうような拡張メソッドを作って、IAsyncEnumerable<Task<T>> から IAsyncEnumerable<T> に戻すようなものを作ればいい。作るのは、まあ、難しくはないのだけど結局yield returnがないので完全手書きしなきゃならないので面倒なので例は割愛。

もしくはSelectなどのselectorに、ラムダ式がasyncで書かれている場合(戻り値がTaskになっている場合)はawaitするようなオーバーロードがあっても良かったと思うのですがねえ。んー、まあ、それはさすがに大きなお世話っぽいからダメか……。

もしくは、ToArrayなどで外に出すのではなく、ForEachAsyncというメソッドが用意されているので、それを使ってawaitするか。こうなると内部イテレータとやってること同じになってきますが。

とはいえともかく、例に出したAwaitメソッドのような、そういうのがないと、実用性に欠けると言わざるを得ません。また、これはあくまでもAsyncEnumerableであって、IEnumerableへのLINQ to Objectsとは別物です。Select, Where, Firstなどは挙動が同じというだけで、中身は全然違います。で、当然ながらコンセプト実装なので、内部的にもあまりこなれてませんね。なので、Ix_Experimental-Asyncは所詮はコンセプト実装であり、面白いとは思いますし満喫はしましたが、実世的なものとは言いがたいと結論づけます。

IObservable<T>の中へ

と、ここまで見てきたので、最後はRxで〆ましょう。ちなみにRxはバージョンが幾つかありますが、私としてはRx 2.0-RCしか使う気はありません。正直、1.0系とは雲泥の差ですからねえ。

static IObservable<DbDataReader> EachReaderAsync(DbConnection connection, string query)
{
    // CreateAsyncで作る。OnErrorなどは手書きですが、十分に書きやすい
    return Observable.CreateAsync<DbDataReader>(async observer =>
    {
        try
        {
            using (var command = connection.CreateCommand())
            {
                command.CommandText = query;
                if (connection.State != ConnectionState.Open) await connection.OpenAsync();

                using (var reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess))
                {
                    while (!reader.IsClosed && reader.Read()) observer.OnNext(reader);
                }
            }
        }
        catch (Exception ex)
        {
            observer.OnError(ex);
            return;
        }
        observer.OnCompleted();
    });
}

static async Task Test()
{
    using (var conn = new SqlConnection(ConnectionString))
    {
        // Rxなので完全にLINQ、awaitableなのでToArrayをawaitしたりも出来るのでノリは完全に一緒
        var result = await EachReaderAsync(new SqlConnection(ConnectionString), "select * from Sales.Customer")
            .Select(x => new
            {
                CustomerID = x.GetFieldValue<int>(0),
                PersonID = !x.IsDBNull(1) ? x.GetFieldValue<int?>(1) : null,
                StoreID = !x.IsDBNull(2) ? x.GetFieldValue<int?>(2) : null,
                TerritoryID = !x.IsDBNull(3) ? x.GetFieldValue<int?>(3) : null,
                AccountNumber = !x.IsDBNull(4) ? x.GetFieldValue<string>(4) : null
            })
            .ToArray();

        // もちろん、そのままSubscribeしたり、FirstOrDefaultAsyncしたりしてもいい
    }
}

というわけでRxでも生成はしやすいし、出力結果も相当扱いやすい。素晴らしい!けれど、これもIAsyncEnumerableと同様にSelectの中でasyncしちゃうと厄介なことになるので、基本的にはできない。

Await拡張メソッドを作るのは簡単だけど、Rxの場合はスレッドセーフの問題が結構難しくて、うまく決めるのは難しい……。

それに加えて、ほとんど決まりきっている、ただたんに列挙したいだけ、のものにRxを使うというのはやり過ぎ感があり、性能面でちょっとね、と。うーん、言うほど悪くはないし、今まで散々持ち上げといてなんだよそれって感じですが、目の前にTaskが転がっていて、そこまで利点が大きくない中で選ぶか?と迫られたら、選びにくいなあ、って。思ってしまうのです。

おまけ:Entity Framework 6について

Entity Frameworkはロードマップによりバージョン6から非同期対応すると明言しています。また、デザインノートTask-based Asynchronous Pattern support in EF.も公開されていたり、それとEFはソースコードが公開されているのですが、CodePlexから最新版を落としてくれば、そこには既にTaskによる実装が存在しています。

軽く見たところ、EF内部で使う用のAsyncEnumerableを定義してありました。それを使って、非同期系を動かしてましたね。ただ、完全に内部用なので外からは使えないし、色々限定的ですけれど。また、最終的に出力する場合は、やはり内部イテレータ的に、await ForEachAsyncしてListに変換するなりしていました。ふんふん、なるほどねー、と、結構眺めてて面白いのでお薦めです。

まとめ

C# 5.0で非同期は簡単になった!そして、同様に非同期はやはり難しい!こうして案を見ていったわけですが、結局どれを選ぶの?というと、まあ、内部イテレータ案が一番無難で良いと思います。

それにしても、いやあもう頭ぱっつんぱっつんです。で、何でDBがネタになっているかというと、新Micro-ORMライブラリを作成中で(DbExecutor v.Next)、今から作るなら非同期対応しないとかありえないよねー、ということで色々考えてはいるんですが、これが中々にビシッ!としっくり決めるのが大変で。結構良い感じにはなってきてつもりではあるんですが、全然まだまだで。あんま人に見られたくない段階なんですが諸事情で見れる人には見れるようになってしまってて恥ずかちい。

そんなわけでVS2012登場まで、もうすぐです(MSDN会員は一週間切ってる、そうでない人も一ヶ月後)。C# 5.0への備え、できてますか?さあ、カオスな非同期時代に突入しましょう!

XboxInfoTwit - ver.2.4.0.4

Xbox.comが内部的にちょっと変わっていて動かなくなっていたので、それに対応しました。多分!

ちょっと今シンガポールに来ていまして、Xbox360の実機がないので動作確認してません!うわぁぁぁ。shoganaiので、ちょっとこちらでXbox360を調達しようか割と考え中。まあともあれ、動いたか動かなかったか報告頂ければ嬉すぃなあ。

linq.js ver.3.0.0-beta, メソッドlowerCamelCase化、など

ずっとやるやる詐欺だったlinq.js ver.3ですが、ようやく始まりました!

トップページのボタンはstableのものになるので、DOWNLOADSタブからver.3.0.0-betaを選んでください。また、NuGetを使っている人はInstall-Package linq.js -Preで入ります。他にlinq.js-jQuery -Pre, linq.js-RxJS -Pre, linq.js-QUnit -Preなどり。

lowerCamelCase化

はい。ようやくJavaScriptらしくなりました。UpperCamelCaseにはC#っぽいとか、キーワードで衝突しなくなるとか、ちょっとした利点はなくもないのですが、そもそも.NETっぽくないほうがいい、というかJavaScriptの世界にちゃんと馴染ませたいという思いのほうが強い。そして、.NETの人以外にも使って欲しくて。

Enumerable.range(1, 10)
    .where(function(x){ return x % 2 == 0})
    .select(function(x){ return x * x});

当然ながら超破壊的変更です。ver.2までのコードは一切動かなくなります。やりすぎですが、しょうがない。痛くてしょうがないけれどしょうがない。さて、ならばとついでにメソッド名の見直しもしました。

Return -> make
CascadeBreadthFirst -> traverseBreadthFirst
CascadeDepthFirst -> traverseDepthFirst
BufferWithCount -> buffer
ToString -> toJoinedString
Do -> doAction
Let -> letBind
MemoizeAll -> memoize
Catch -> catchError
Finally -> finallyAction
ToJSON -> toJSONString

これを機に、というかこういう機会じゃないとやれないですから。toStringやtoJSONは、上書きしてしまうとマズいので別名にしています。toStringは、まあそのままなので分かると思うのですが、toJSONのほうは、JSON.stringifyで特別扱いされるメソッドなので、こっそり注意が必要なんですね、というか実際ハマッて気づくのに時間かかりました。

extendTo

prototype.js以降、prototype拡張は悪、でしたが、最近のJavaScriptはfor inで列挙しない形での拡張(Object.definePropertyでenumerable:falseにする)が可能になっています。それを全面に押し出したSugarといったライブラリもあり、確かに便利なのですよね。

さて、linq.jsでは配列などをLINQで扱うためにEnumerable.fromで包んでやる必要があって面倒でしたが、配列からそのままselectとかwhereとかが生えていれば、便利、ですよね?なので、任意に拡張できるようにしました。

// Arrayを拡張する
Enumerable.Utils.extendTo(Array);
        
[1, 3, 10, 1000].where("$%2==0").select("$*$");

Enumerable.Utils.extendToを一度呼べば、from不要で直接LINQのメソッドを呼ぶことができます。もしブラウザがObject.definePropertyに対応していなければ、その時はprototypeを直接拡張しています。

さて、LINQのメソッド名とネイティブのメソッド名が被っている場合は、ネイティブのメソッド名を優先して、上書きはしません。例えばjoinとか、concatとか。その場合はByLinqがメソッド名の末尾につきます。joinByLinq、など。

// 名称が被るものはByLinqというプリフィックスがつく
[1, 3, 10].reverseByLinq();

// もしくはasEnumerableメソッドを呼んであげればLINQのメソッドのみになります
[1, 10, 100].asEnumerable().forEach(function(x, index){alert(x + ":" + index)});

forEachなどは古いブラウザではそのまま、新しいブラウザではforEachByLinqになる、といったようにブラウザ互換性がなくなるので、個人的にはByLinqの形で呼ぶよりかは、asEnumerableを使うことのほうをお薦めします。

Visual Studio 2012でのIntelliSense超拡張

VS2012でlinq.jsを使うと、ただでさえ充実していたIntelliSenseが更に超補完されます。どのぐらい補完されるか、というと、selector関数でオブジェクトの候補が並んでしまうぐらいに。

もはや完全にC#。あまりの快適さにチビる。勿論、↑の図ではFooは文字列なので、x.Foo.で文字列のメソッド候補がIntelliSenseに並びます。動的言語とは思えない超補完っぷりがヤバい。そして入力補完が最大限に活きるように設計されているLINQなので、組み合わさった時の快適度は半端ない。

Chaining Assertion for QUnit

ユニットテストを書く際に、equal(actual, expected)と書くのが嫌いでした。どちらがactualなのかexpectedなのか一瞬悩むし、そもそも外側から包むのがかったるくて。かといってshouldといった、英語的表現なのも冗長なだけで全く良いとは思っていませんでした。そこでC#ではChaining Assertionといった、actual.Is(expected)でアサートが書けるライブラリを作ったのですが、それをJavaScript用に移植しました。

// 流れるように.isと打ち込むだけ
Math.pow(10, 2).is(100); // strictEqual(Math.pow(10, 2), 100)

// コレクションに対する適用は可変長にカンマ区切りで値を並べるだけ。勿論、配列にも使えます。
Enumerable.rangeTo(10, 15, 2).is(10, 12, 14); // deepEqual(Enumerable.rangeTo(10, 15, 2).toArray(), [10, 12, 14])

// LINQと組み合わさることでコレクション系のチェックが遥かに容易になる!
[1, 5, 10].all("$<12").isTrue(); // collection assertion with linq.js!

といった感じに書けて、超楽ちんです。使うにはlinq.qunit.jsを別途読み込んでください。

その他

createEnumerable, createEnumerator, createLambdaといった、自作Enumerableメソッドを作るための道具を外部公開するようにしました。是非作っちゃってください。

Enumerable.Utils.createLambda
Enumerable.Utils.createEnumerable
Enumerable.Utils.createEnumerator

更に、メソッドも追加されています。

Enumerable.defer
asEnumerable
merge
choose
isEmpty
distinctUntilChanged
weightedSample
log

それらの細かい使い方などは追々書いていきます。また、merge, zip, concatは複数のシーケンスを引数に受け取れるようになりました。

そして、C#では、以前にneue cc - LINQのWhereやSelect連打のパフォーマンス最適化についてという記事を書いた通り、Where連打やSelect連打、それにWhere->Selectといったよくあるパターンに対して最適化が入っているのですが、それをlinq.jsでも再現しました。なので、Where連打などによるパフォーマンス劣化が抑えられています。また、頻出パターンのWhere->Selectで高速化されたのはかなり大きいと思っています。

それに加えてrange, rangeDown, rangeToといったよく使う生成関数の速度を大幅に上げました(以前はtoInfinity().take()で生成していたのを、独自生成に変更したため)。

なので全体的にパフォーマンスも向上しています。

それと最後に、jQueryのプラグインとしてのものは今回からやめました。なんか混乱するし意味ないな、と思ったので、jQueryとの連携はlinq.jquery.jsによるtoEnumerable/tojQueryを追加するファイルのみとなっています。RxJSに関しても最新版のRxJSと連携できるようにしました(linq.rx.js)

今後

VS2012に対するIntelliSenseの充実化がまだ1/5ぐらいしか出来ていないので、それの充実が優先です。あと、リファレンスやサンプルが書けてないので追加。それらが出来たら、いったんver.3として正式公開します。プログラミング生放送勉強会 第17回@品川 #pronama : ATNDで話すつもりなので、その日、8/25までには正式公開を目指します!というわけで是非是非聞きに来てください。

あ、あとnode.js用にnpm公開も、ですね。

Reactive Extensions + asyncによるC#5.0の非同期処理

Reactive Extensions(Rx)の利点ってなんですかー、というと、合成可能なんです!ということです。合成可能って何?というと、LINQが使えるということなんです!です。じゃあ他には、ということで…… 詳しくはこの動画/スライド見るといいです。 Curing Your Event Processing Blues with Reactive Extensions (Rx) | TechEd Europe 2012 | Channel 9。最初のほうの例が非常に分かりやすいので、とりあえずその部分だけ引っ張ってきますと

// sender, argsの型がふわふわ
exchange.StockTick += (sender, args) => // senderの型が消えてる
{
    if (args.Quote.Symbol == "MSFT")
    {
        // 合成できないからイベントの中でベタ書きしかない
    }
};

exchange.StockTick -= /* ラムダ式でイベント登録すると解除不能 */

これが通常のイベントの弱点です。Rxにすると

// <Quote>という型が生きてる
IObservable<Quote> stockQuotes = ...; // 変数に渡せる

// LINQクエリ演算子が使える
var msft = stockQuotes
    .Where(quote => quote.Symbol == "MSFT");

var subscription = msft.Subscribe(quote => /* */);

// イベント解除が容易
subscription.Dispose();

といった感じで、実に素晴らしい!じゃあEventはもうObsoleteでいいぢゃん、というと、まあいいと思うのですがそれはそれとして、C#ネイティブだからこそデザイナがイベントが大前提で考慮されていたり、軽くて実行速度が良かったり、といったところは勿論あります。あとRxだとイベントのIObservable化が面倒だとかもね。この辺は最初から言語サポートの効いてるF#のほうが強いんですよねー。

非同期のリトライ

Visual Studio 2012も、もうRCということで間近感が相当にあります。一方でReactive Extensions v2.0 Release Candidate available now!ということで、こちらも間近感があります。一度2.0使うと1.0には戻れないよ!(NuGetではRx-Main -Preで入れられます)

じゃあRx 2.0の紹介でもしますかー、というと、しません!(ぉぃ)。その前に、asyncとRxの関係性にケリをつけておきましょう。

で、asyncの非同期とRxの非同期はやっぱり使い分けフワフワッという感じ。複数の値が来るときはRxでー、とか言われても、そもそも複数っていうのがそんなにー、とか。あと、それ以外にないの?というと、Rxの合成の強さが非同期にも発揮してRetry処理とか柔軟でー、とか。確かにそれっぽい。けれど、どうもフワッとしてピンと来ないかもしれない。

ので、例を出していきましょう。まず、リトライ処理。リトライ処理を素の非同期で書くと泣きたくなりますが、C# 5.0を使えばasync/awaitで何も悩むことなくスッキリと!

static async Task<string> DownloadStringAsyncWithRetry(string url, int retryCount)
{
    var count = 0;
RETRY:
    try
    {
        count++;
        var req = WebRequest.CreateHttp(url);

        using (var res = await req.GetResponseAsync())
        using (var stream = res.GetResponseStream())
        using (var sr = new StreamReader(stream))
        {
            return await sr.ReadToEndAsync();
        }

    }
    catch
    {
        if (count >= retryCount) throw;
    }
    goto RETRY;
}

static void Main(string[] args)
{
    var google = DownloadStringAsyncWithRetry("http://google.com/404", 3);
    Console.WriteLine(google.Result);
}

簡単です。さて、ではこれをRxで書くと……

static async Task<string> DownloadStringAsyncWithRetry(string url, int retryCount)
{
    var req = WebRequest.CreateHttp(url);

    // retry処理は.Retryで済む
    using (var res = await req.GetResponseAsync().ToObservable().Retry(retryCount))
    using (var stream = res.GetResponseStream())
    using (var sr = new StreamReader(stream))
    {
        return await sr.ReadToEndAsync();
    }
}

はい。別にRxとasyncは排他じゃありません。使って効果のあるところに差し込んで、Mixしてやれば、ただでさえ強力なasyncが更に強力になります。TaskとIObservableは変換可能なので、ToObservableして、あとはRetryメソッドを繋げるだけ。そしてIObservableはawait可能(LastAsyncと同じ効果で、最後の値を取る。非同期処理の場合は値が一つなので問題なし)なので、まんまawaitしてasyncと繋げてやればいい。素敵ですねー。

が、上のコードはちょっと間違ってます。どこが間違っているか分かりますか?

エラーの帰ってくるページ(google/404などは404エラーを返してくれるのでテストに楽←別に500にすれば500を返してくれるわけじゃなくて、ただたんに存在しないページだから404なだけで、別にどこでもいいです)を指定してFiddlerなどで観察すれば分かりますが、一回しかリクエスト飛ばしません。Retry(3)としても一回しか飛んでいません。ちゃんとRetryは3回しているのに。

どういうことかというと、GetResponseAsync()の時点でリクエストに失敗しているからです。失敗済みのリクエストに対しては、何回Retryしても失敗しか返しません。ここは本当にはまりやすくて注意所なので、よく気を付けてください!

解決策は、Retryで生成を毎回やり直すこと。Deferで包めばいいです。

static async Task<string> DownloadStringAsyncWithRetry(string url, int retryCount)
{
    // Retry時に毎回WebRequestを作り直す
    var asyncQuery = Observable.Defer(() => WebRequest.CreateHttp(url).GetResponseAsync().ToObservable())
        .Retry(retryCount);

    // retry処理は.Retryで済む
    using (var res = await asyncQuery)
    using (var stream = res.GetResponseStream())
    using (var sr = new StreamReader(stream))
    {
        return await sr.ReadToEndAsync();
    }
}

ちょっと罠があるしコードも増えてしまったけれど、それでも、まあ、まだ割といいかな、って感じでしょうか?

さて、リトライは即時じゃなくて一定間隔置いた後にリトライして欲しいってことが多いと思います。同期処理だとThread.Sleepで待っちゃうところですが、それはちょっとスレッド勿体ない。C# 5.0からはawait Task.Delayを使いましょう。

static async Task<string> DownloadStringAsyncWithRetry(string url, int retryCount, TimeSpan retryDelay)
{
    var count = 0;
RETRY:
    try
    {
        count++;
        var req = WebRequest.CreateHttp(url);

        using (var res = await req.GetResponseAsync())
        using (var stream = res.GetResponseStream())
        using (var sr = new StreamReader(stream))
        {
            return await sr.ReadToEndAsync();
        }

    }
    catch
    {
        if (count >= retryCount) throw;
    }

    if (retryDelay > TimeSpan.Zero)
    {
        await Task.Delay(retryDelay); // これで待つ
    }

    goto RETRY;
}

以前のものにTask.Delayを足しただけで簡単です。わーお、素晴らしい、なかなか強力強烈です。ではRxは、というと、同じように遅延する演算子を足すだけ。Delay、ではダメでDelaySubscription(Rx 2.0から追加)を使います。

static async Task<string> DownloadStringAsyncWithRetry(string url, int retryCount, TimeSpan retryDelay)
{
    // DelaySubscriptionで遅延させる
    var asyncQuery = Observable.Defer(() => WebRequest.CreateHttp(url).GetResponseAsync().ToObservable())
        .DelaySubscription(retryDelay)
        .Retry(retryCount);

    using (var res = await asyncQuery)
    using (var stream = res.GetResponseStream())
    using (var sr = new StreamReader(stream))
    {
        return await sr.ReadToEndAsync();
    }
}

できました!できました?いや、これだと初回リクエスト時にも遅延されちゃってて、ちょっとイケてない。修正しましょう。

// 外部変数用意するのがダサい
var count = 0;
var asyncQuery = Observable.Defer(() => WebRequest.CreateHttp(url).GetResponseAsync().ToObservable())
    .Let(xs => count++ == 0 ? xs : xs.DelaySubscription(retryDelay))
    .Retry(retryCount);

はい、ダサいし、なんだか何やってるのかさっぱりになってきました、サイテー。LetもRx 1.0にはなくて(それ以前にはあったのですが削られた)2.0から復活になります。Letは、一時変数を置かなくて済むというチェーン病にかかった人がお世話になる処方薬です。んなもん読みにくくさせるだけじゃねーか、という感じですが、もしLetがないと変数を置いて var xs = ToObservable(); xs = () ? xs : xs.Delay..; xs = xs.Retry(); としなければならなくて、非常に面倒くさいのです。だから、使いどころを守って乱用しなければ、割とイケてます。結構大事。

が、しかし、これも間違っています!(えー)。というかLetではなくて変数に展開してみるとオカシイとはっきり分かるのですが、Letの内部はRetryとか関係なく一回しか評価されないので、これだと必ずDelaySubscriptionなしのほうしか通りません。この路線で行くなら、更にやけくそでDeferを追加しましょうか。

// Deferだらけとかダサすぎるにも程がある
var count = 0;
var asyncQuery = Observable.Defer(() => WebRequest.CreateHttp(url).GetResponseAsync().ToObservable())
    .Let(xs => Observable.Defer(() => count++ == 0 ? xs : xs.DelaySubscription(retryDelay)))
    .Retry(retryCount);

ダサすぎて話にならない。Defer連打ダサい。Deferまみれになったら、ちょっと根本から方針を疑いましょうか。ついでに外部変数を使うというのがそもそもダサい。もう少し頑張りましょう!クエリ演算子をこねくり回して、と。

// DelayなしのDeferとDelayありのDeferを連結して、DelayありのみをRetryさせている
var asyncQuery = Observable.Defer(() => WebRequest.CreateHttp(url).GetResponseAsync().ToObservable())
    .Let(xs => xs.Catch(xs.DelaySubscription(retryDelay).Retry(retryCount - 1)));

どうでしょう。他にもやりようは色々とあるかもですが、正直ワケガワカラナイのでこの辺でよしておいたほうがマシです。実際のところ、以下のような拡張メソッドを作るのがベストだと思っています。

// 結局これが一番なのではかという結論に至る
public static async Task<string> DownloadStringAsyncWithRetry(this WebClient client, string url, int retryCount, TimeSpan retryDelay)
{
    var count = 0;
RETRY:
    try
    {
        count++;
        return await client.DownloadStringTaskAsync(url);

    }
    catch
    {
        if (count >= retryCount) throw;
    }

    if (retryDelay > TimeSpan.Zero)
    {
        await Task.Delay(retryDelay);
    }

    goto RETRY;
}

new WebClient().DownloadStringAsyncWithRetry("hogehoge"); だけですからねー。拡張メソッド万歳。Rx最終形のような短さとか魔術っぽさはゼロで面白くも何ともない、というか正直クソつまらないコードなわけですが、そこがC#のC#たる所以ですな、ということで。私はRxのようなクールさも勿論大好きなのですが、こういうイモさもまた、C#らしさであって、現実をより良くするための、目的を忘れない素敵な側面だと思っています。

ちなみにWebRequestの場合はそれ自体の作り直しが必要なので(一度エラーを受けたら何度GetResponseを繰り返してもダメぽ)、拡張メソッドダメです。WebClientはイベントベースなのでTask系と相性がアレで今一つなわけですが、WebRequestはWebRequestで、これベースに拡張メソッドだけで整えるのは無理があるのですね……。

.NET Framework 4.5からはHttpClientというクラスが入るので、それを使うとちょっとだけモダンっぽい雰囲気。

// モダンなドトネト的にはHttpClientかしら
public static async Task<string> GetStringAsyncWithRetry(this HttpClient client, string url, int retryCount, TimeSpan retryDelay)
{
    var count = 0;
RETRY:
    try
    {
        count++;
        return await client.GetStringAsync(url);
    }
    catch
    {
        if (count >= retryCount) throw;
    }

    if (retryDelay > TimeSpan.Zero)
    {
        await Task.Delay(retryDelay);
    }

    goto RETRY;
}

別途System.Net.Httpの参照が必要なのが面倒ですが。

非同期のタイムアウト

Rxが色々できるのは分かったけれど、結局そういう部分って拡張メソッドとかに隔離してアプリケーションコードからは離れるので、やっぱそんなでもないんじゃないの!?というと、あー、まあそうかもねえ、とか思いつつ、複雑になればなるほど効果は加速しますが、そうなるとRxでも(見た目はスッキリしたとしても)やっぱ複雑ですからね。さておき、このままだとアレなのでもう少しまともな例を、一番最初に挙げたCuring Your Event Processing Blues with Reactive Extensions (Rx) | TechEd Europe 2012から引っ張って来ましょうか。

タイムアウトを追加する例です。WebRequestだとTimeout設定すればいいぢゃーん、ではあるものの、そうではないシチュエーションも沢山ありますから、対策を知っていて損はないです。まず、asyncの例を。

static async Task<string> GetHtmlAsync(Uri url)
{
    var client = new WebClient();

    var download = client.DownloadStringTaskAsync(url);
    var timeout = Task.Delay(TimeSpan.FromSeconds(30));

    // これ結構トリッキーですよね
    if (await Task.WhenAny(download, timeout) == timeout)
    {
        throw new TimeoutException();
    }

    var html = await download;
    return html;
}

WhenAnyが中々トリッキーですね。慣用句として覚えてしまえばどうってことないのですが……。asyncもただawaitするだけじゃなくて、ちょっと慣れてきたらTask.WaitAll/Any, Task.WhenAll/Anyを使いこなすと、性能的な意味でも表現力的な意味でもグッと広がりますので、探究するのがお薦め。

さて、それをRxでやると……

static async Task<string> GetHtmlAsync(Uri url)
{
    var client = new WebClient();

    var download = client.DownloadStringTaskAsync(url)
        .ToObservable()
        .Timeout(TimeSpan.FromSeconds(30));
            
    var html = await download;
    return html;
}

ToObservableして、Retryの時のようにTimeoutを足すだけ。非常に直観的で、楽ちん、分かりやすい。演算子が豊富なのはRxの強みです。だからRetryにTimeoutがつけられるオーバーロードが最初から用意されていれば、Letとかで複雑になってしまった例もスッキリ仕上がって、ドヤァと言えたんですけどね(笑)

// こういうのを作っておけば!
static IObservable<T> Retry<T>(this IObservable<T> source, int retryCount, TimeSpan retryDelay)
{
    return source.Catch(source.DelaySubscription(retryDelay).Retry(retryCount - 1));
}

// 神がかってシンプルに!Rx最強!
public static async Task<string> GetStringAsyncWithRetry(this HttpClient client, string url, int retryCount, TimeSpan retryDelay)
{
    return await Observable.Defer(() => client.GetStringAsync(url).ToObservable()).Retry(retryCount, retryDelay);
}

標準で足りない演算子は自分で作ればいいので、また、asyncが出来たことで、今まで自作が大変だった演算子も作るのが大分容易になりました!ので、ガンガン作ってしまうといいです。汎用的に使える演算子が集まれば集まるほど、Rxの合成可能という性質が価値を発揮します。

リトライやタイムアウトをC# 4.0でRxなしで書くと

死ぬほど面倒なので書きません。いや無理でしょ常識的に考えて。

まとめ

というわけで、Rxとasyncは手を取り合って仲良く高みを目指せばいいわけです。使いこなしが必要なのはどっちも変わらない!

さて、@ITの連載、Reactive Extensions(Rx)入門 - @IT の次回は非同期のはずですが(聞こえなーい)、ええと、はい、すみません……。ええと、あと次はRx 2.0の強化事項を、ええと、まあそのうちいつか……。はい、すみません。

諸事情あって今色々詰まってて本気でヤバいんですが、それはそれとして、現在全力で一年以上やるやる詐欺だったlinq.jsの改修を進めていまして、これは本当に本当に絶対近日中にベータを出すのでお楽しみに。相当にイイ出来で、割と革命的に凄い内容になってます。いやほんと。かなり自信ありますよ。

他の積みタスクは、ReactiveOAuth(バグ修正のPull Requestを放置中というサイテーな有様、OAuth 2.0対応しないの?とか)、ReactiveProperty(WinRT対応まだー?)、Utakotoha(現在動いてない模様なので要改修)、DbExecutor(全面再構築まだー?DataSet殺すんでしょー?)とかでしょうか、って結構ありますね、うわぉぅ。というかReactive系は2.0対応とWinRT対応をやらなきゃならないので作業量的に面倒くさくて、ついつい手が遠ざかってしまいですね。はい、でも、やります。

にゃー、という感じでブログも結構アレなWebFormsとDataSetディスもそろそろさようならして、通常営業に戻ってきませう。

DataSetについて

けちょんけちょんに言ってるとか言ってないとかで言えば言ってるので、遅まきながらその理由などをつらつらと。正直なところDataSetなんて現代の観点から使ってみれば、一発でどれだけクソなのか自明だろう、ぐらいに思ってたので別に言うまでもないと思ってたので特に述べてなかったのですが、意外と支持の声も大きいのですね。困惑するぐらいです。

DataSetというと型付きと型無しがありますが、形無しのほうは、もういらないんじゃないかな。カジュアルな用途ならExpandoObjectを使ってくれという感じだし、そうでないなら、C#で型無しのヘヴィな入れ物とか利点を損ねるしかないわけで。せめてdynamicに合わせた作り直しが必要よね。

それでもADO.NETと密接に結びついていて、たとえばSqlBulkCopyはDataTableしか受け取らないなどがある。だから必要か、というと、そうじゃあなくて。そうじゃなくて、それは害悪なんだって。そのせいでストリームで流し込めないし。今時だったらIEnumerableに対応していて欲しいところだというのに(なお、専用のIDataReaderを手作りすればストリームで流し込めます)。腐った現状を肯定するんじゃなくて、どうあるべきなのかを認識しよう。

ちなみにLINQ to DataSetは型無しDataSetのためのキャスト要因でしかないので、ほとんど名前だけでドーデモイイ代物です。型付きDataSetのほうは一応IEnumerable<TRow>なので不要なんですよね。

さて、話の主題のStrongly Typed(笑) DataSetのほうは、死んでほしい。今すぐに跡形もなく消え去ってほしい。なんでそうも恨み言が多いのかと言ったら仕事で割とヘヴィに使い倒しているから、なのですけれど。

Nullableに非対応

分かりやすく最大の馬鹿げた点はここですね。マトモな神経ならどれだけ頭可笑しいのか分かるはずで。作られた年代が年代だからしょうがない?いや、今話しているのは現代のことで、そんなNullable非対応のまま更新されず、大昔に見捨てられた代物なんてどんな選ぶ理由あって?

なお、型付きDataSetを知らない人に説明すると、nullが入る可能性のある列に対してはIsHogeNullというメソッドが生成されているので、そちらで事前チェックすればいい、というシステムになっています。if(row.IsHogeNull()) row.Hoge; といった感じ。もしnullの状態でrow.Hogeにアクセスすると実行時例外。

これ、すごく気持ち良くないんですよね。型付き言語の良さって型がドキュメントなことであり、C#の良さってそれがIntelliSenseでコードを書いている最中からリアルタイムに立ち上がって教えてくれるところであって。なんでコード書いてIntelliSenseも出ているのに、それがnullが混じる可能性があるのかないのか分からないの?Hogeの型がNullableならば、そこから伝わるのに。こういうC#の利点を損なうような代物は全力で許さない。

Enumに半分非対応

データベースの数値とC#上のEnumを関連付けることは割とあるシチュエーションだと思うわけですが(EntityFrameworkでもずっと要望に上がっていて最近やっとようやく対応しましたね……)DataTableもプロパティに関してはDBの型ではなくEnumに変更できます。ただし、TableAdapterによるメソッドの引数のほうは変えられないんですねー、あははぁ、intだー、intだぁー、凄いね、キャストだね。クソが。

ただたんにキャストすればいいだけだから大したことないぢゃん、と思うかもですが、これは非常に大きなことなのです。タイプセーフ、というだけじゃなくて、引数の型がEnumだと、それに沿うようIntelliSenseの第一候補として優先的に表れてくれて、こういう些細な気配りがC#の気持ちのよいプログラミングを支えているのです。

これでどこがTypedなのか。ありえないレベル。

使えないデザイナ

デザイナ、重いんだよね、普通に。激しくストレスなぐらいに。そして位置の設定はすぐに吹き飛んで横一列に並びきった整列へ。重い状態でセーブするとDesginer1.cs, Designer2.csと数字が延々とインクリメント。そして、長大奇怪なXMLに保存されるのでコンフリクトが発生したらマージ不能。OK、DataSetは古の悪名高きVisual SourceShredder(ロック方式なのでコンフリクトは原理上一応発生しない)とセットで使うべきものなんだな、それならばしかたがない。つまり、現代で使うべきものではない。

そして、基本的にクエリはこのデザイナから書かせるものなのですが、うまくSQLを解釈してくれない。ちょっと凝ったクエリを書くだけで、機能しなくなる。例えばSQL Serverの共通テーブル式とかうまく作れない。生SQLを書かせるのに、シンプルなSQLしか書けない。whereのin句にパラメータを並べるとかもできない。それなら逐語的文字列で書かせてもらったほうが百億倍マシだわ。というか書かせろという感じですが。(できなくもないですけれどね、ただもうそれならそもそもDataSet使わなくていいぢゃん、ほかの余計な制約もあるのだから、といったところで)。

お節介DataRowView

私の今の主戦場はWebFormsなのですが、RepeaterにDataTableをバインドすると、あら不思議、DataRowがDataRowViewに化ける!わー、嬉しいー、死ね。余計なおせっかいとしか言いようがない。これ、まあ現代的なC#erならばDataTableをLINQ使って加工したのをバインドしたりもするわけで、IEnumerable<DataRow>の場合は、そのままのDataRowが来る。ええ、同じはずの型が、違う型でやってくるなんて、悪夢すぎる。狂ってる。

文字列クエリ

Selectメソッド!紛らわしいですが、DataTableのSelectはLINQにおけるWhereにあたります。「文字列」でクエリ書かせるものが存在します。おお、文字列、タイプセーフじゃあないねえ……。型付きDataTableであっても戻り値は型無しDataTable、なんだねえ……。すごい、すごいすごい。いらないね。現代的に強化するならExpressionに対応させてタイプセーフなクエリを発行するとか、やりようはあるはずですが2005年で更新止まってるのでそんな高尚な機能が追加されることは未来永劫ないでしょう。

おまけに型付きのDataTableはLINQ to Objectsで扱えるので、素直にLINQ to Objectsにまかせてしまったほうが遥かに良い。LINQ以前は、DataTableってインメモリDBとしてある程度のクエリが簡単に実装できる、というところがあったのですが、LINQ以後の世界では純粋なC#コードとして簡単にソートも射影もフィルタリングも可能、それどころか備え付きのクエリとは比較にならないほど柔軟で強力なクエリ能力を手にしているので、もはや中途半端なインメモリDBは不要で、純粋なコレクションだけで構わないぐらいなのですよね。

モック作るのが面倒くさい

専用のヘルパでも作りこまない限りは絶望的。

じゃあどうするの?

そうですね、ここの回答がない限りはDataSetから抜けられないのですしね。私としてはLINQ to SQLでいいぢゃん(EntityFrameworkじゃなくてね)、と思うのですけれど。MSのコンサルタント連中が2009年末にもなっていま使うべき、学ぶべき.NETテクノロジはどれ?という講演で「まずはデータセットやテーブルアダプタを活用できることが大事、とか」「更新系が弱い」とか言い続けているのが絶望的。なんでDataSetが基礎知識なんだよ、馬鹿じゃねーの。

オールドテクノロジーで縛り付けたいのかしらね。求められるのは、ある程度の弱さを知覚した上でのPOCO+DataContextでの使いこなしかたの説明が求めるわけで。まさか、2012年の現在でもEntityFrameworkは更新に弱くてDataSetがまずは基本ですね、とか言っていやあしないですよね、知らないけど。

何でも得手不得手があって使い分けが大事、とかいうのはすごく簡単な逃げ口上ですが、何にでもメリットデメリット、そして未来の潮流を踏まえたうえでの学習の投資で天秤にかけなければならない。DataSetに未来はどこにあるの?腐臭を放ってる資産の保守ぐらいでしょ。こういう影響力ある人らがどうしょうもないことを言うのには、猛烈に腹が立っていてずっと不信感しか持てない。今のところ最後の赤間本であるLINQ本も急いで作った感バリバリでとてもひどいしね(そのことが前説にも書いてあるしね!影響力があるのは分かっているのでしょうから、もう少し丁寧に書けなかったものなのか)。

まあ、WebFormsやWinFormsにはDataSetを前提においたコントロール資産が山のようにあるから……。というのは移れない理由にはなるでしょうね。その場合はプラットフォームごとサヨウナラするしかないんじゃないの?そこまでは知りませんよ。で、その完全に縛られたポトペタ成果物って、魅力的なの?公に出たときに競争力あるの?年々、競争力を失っていっていると思うんですよね。それが許される賞味期限はとうに過ぎていて、残っているのはガラクタだけ。

そして、これからは定型的な業務アプリへならLightSwitchも出てきましたしね(VS2012からは標準搭載で、出力先もSilverlightだけじゃなくHTML5が選べるので実用性高くなったと思う)

じゃあEF使えばいいの?

LINQ to SQLは更新されていなくて、今のMSが推してるデータアクセステクノロジはEntityFrameworkだからEF使おう、というと、うーん、私はEntityFrameworkあんま好きくないので、そんなに薦めないかなあ、とか言っちゃったりして。EntityFrameworkの思想に一ミリも魅力を感じないので。LINQ to SQLのほうがまだずっといいよ!更新されてないぢゃん、というならDataSetだって一緒だしさ!なんというか、DataSetといいEFといい、ADO.NETチームってとってもセンス悪いんじゃないか、と思ったり。(ちなみにLINQ to SQLはC#チーム側からの実装だそうで、さもありなん)。あと同じくセンス悪いなーって思うのはEnterprise Libraryとかですね!

ORMは信用ならねえがDataSetはクソだから、もはや生ADO.NETで、つまりDbConnectionからDbCommandでDbReaderで、というので手作業しかねえ!というのはあると思いますが、うーん、手作りはナシね、ナシ。生を生のまま扱うのはアレなので、ちょっとしたユーティリティ、独自マッパーっぽいものは作ると思うのですが、これがねえ。私は以前に、センスのない独自マッパーを使わされていたことがあったのですが、使いにくくて結構な不幸でした。

単純にマッピングする薄い代物だとはいえ、作るにはそれなりなセンスと技量が必要なのです。で、そういうのをMicro-ORMと称しています。生ADO.NETのちょこっとだけ上層にあって主にクエリ結果のマッピングを効率よく行う、程度な代物なので、実質的には生ADO.NETを扱ってると考えてもいいです。現在だと代表的なものにdapperとか、色々と良いものがあるので、それらを選べばいいんじゃないですか。

フルORMにしたって、Microsoft純正以外にもLightSpeedとか良い選択肢がありますよ。NHibernateはどうかと思いますが。

ORMについてはLightSpeedの作者の語るORMのパフォーマンス最適化という記事が良いと思います。特にDataSetからの移行を意識するのならば。LINQ to SQLの成り立ちについてはThe Origin of LINQ to SQL を訳してみた - NyaRuRuの日記を。

Micro-ORMによるデータコンテキスト

Micro-ORMは、当然ながらDataSetやORMが持つ作業単位の保持はないので、そういうのが必要だったら、ある程度は手作業で作りこむ必要はあります。

少し実例を挙げると私が作っているというか作ったものは、データ保存先がDBだけじゃなくてMemcached(キャッシュとしてだけじゃなくデータ保持にも使う)だったりRedis(ListやHashなどのデータ構造を持つKVS)だったりが、それぞれのパフォーマンス的に適していると思える箇所に挟み込まれたデータコンテキストをなしていて、一個のDBだけの世界を構築するORM系はどれも不適切でして。

かといってDapperなどの既存のMicro-ORMも若干、某弊社の事情に合わないところがあるので、自分で作ろうかなあ(上で作るな、と言ってたのに!)とはずっと思ってるところですね。ベースになるMicro-ORMは既にある→DbExecutor - Simple and Lightweight Database Executorのと、拡張のアイディアは沢山あるので、あとは実装時間ががが。

まとめ

DataSetは単純に言って古い。言語機能が年々強化されていく中で、2005年の時点でストップしている(しかも2005の時点の言語機能(Nullable)にすら非対応)なものを使うのは、プログラミングにおいて足枷でしかない。7年前ですよ、7年前。あんまり一般化して言うのもアレですが、ドトネトの人って浦島太郎な雰囲気ありますよ、キャッチアップが遅すぎるというか枯れてるのが、とかかんとかって。エンタープライズだとどうだとか業務アプリだとどうだとかメンバーのレベルがどうだとか、そんな言い訳ばかりで、すごく格好悪い。

すっごくクールじゃないわけですよ。そんな言説が目立つところに、魅力を感じるのは難しい。せっかくC#や.NETは魅力的なのに。というわけで、私としては、資産がどうだとかこうだとかって言説は吐きたくないし、もっと活力ある感じになればいいな、って思います。2009年の現実に最も使える.NETのバージョンはどれ?→.NET 2.0が現時点でベスト、とか凄く絶望的じゃないですか。まあ、えんたーぷらいずの世界ではそれだししょうがないというのならそうなのでしょうが、なるべくそうじゃない世界を作りたい。

そのためにも、新しく、負の遺産を作るのだけはナシです。DataSetに別れを。

控えめなViewStateによるハイパフォーマンスASP.NET Web Forms開発

今どきのウェブ開発はMVCだよねー、な昨今を皆様どうお過ごしでしょうか。そんな中であっても、Web Formsでモバイル向けにハイパフォーマンスサイトを作らなきゃいけない時だってあるんです。さて、そんなWeb Fromsですが、とりあえずの敵はViewStateです。ViewStateをどのように活かし、どのように殺害するか、そこに全てがかかっています。幾つかの典型的なシチュエーションを取り出して、ViewStateを抹消していきましょう。

ViewStateMode = "Disabled"

下準備として、ViewStateModeをDisabledにします。ViewStateModeは.NET Framework 4から入った新機能で、「ようやく」ViewStateのオン・オフをルート階層から切り替えることが出来るようになりました。それまではEnableViewStateのみで、falseにすると、その階層の下のViewStateが全てオフになってしまうという使いにくいものでした。全部OFFで済ませられるほど世の中は甘くなく、Web Formsでは部分的にONにする必要性があります。ViewStateModeとEnableViewStateの両方が記述されているとEnableViewStateのほうが優先されて害悪となります。というわけで、.NET Framework 4以降ならばViewStateModeのみを使いましょう。

<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site1.master.cs" Inherits="WebApplication3.Site1" %>

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>ViewState殺害教</title>
</head>
<body>
    <asp:ContentPlaceHolder ID="BodyPlaceHolder" runat="server" ViewStateMode="Disabled">
    </asp:ContentPlaceHolder>
</body>
</html>

まずは、マスターページのContentPlaceHolderに対して、ViewStateModeをDisabledにしましょう。こうすることで全ページが強制的にデフォでViewStateオフが働きます。また、ページ単位でDisabledにしてしまうと、各Pageの this.ViewState["hogehoge"] も無効になってしまって不便なので(PageのViewStateは便利なinput hiddenみたいなものですし、闇雲にすべてをオフにせず、便利なものは便利に使うのが大事です)、マスターページのContentPlaceHolderに仕込むのが最良だと私は考えています。

テキストボックスやドロップダウンリストから値を取り出す

そんなわけで、ViewStateを丸ごとオフにした状態からデータを取り出してみませう。

<%@ Page Title="" Language="C#" MasterPageFile="~/Site1.Master" AutoEventWireup="true"
    CodeBehind="WebForm1.aspx.cs" Inherits="WebApplication3.WebForm1" %>

<asp:Content ContentPlaceHolderID="BodyPlaceHolder" runat="server">
    <form runat="server">
        <asp:TextBox runat="server" ID="ToaruTextBox" />
        <asp:DropDownList runat="server" ID="ToaruDropDownList" />
        <asp:Button runat="server" Text="ただのボタン" OnClick="Button_Click" />
    </form>
</asp:Content>

こんなどうでもいい画面があるとして、コードビハインド側は

public partial class WebForm1 : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (IsPostBack) return;

        ToaruTextBox.ToolTip = "ほげほげ";
        var items = Enumerable.Range(1, 10)
            .Select(x => new ListItem
            {
                Text = x + "点",
                Value = x.ToString(),
                Selected = x == 5
            })
            .ToArray();
        ToaruDropDownList.Items.AddRange(items);
    }

    protected void Button_Click(object sender, EventArgs e)
    {
        try
        {
            Response.Write("TextBox.Text:" + ToaruTextBox.Text + "<br />");
            Response.Write("TextBox.ToolTip:" + ToaruTextBox.ToolTip + "<br />");
            Response.Write("DropDownList:" + ToaruDropDownList.SelectedValue);
            Response.End();
        }
        catch (System.Threading.ThreadAbortException) { }
    }
}

こんな感じとします。ところでどーでもいーんですが、DropDownListに値を突っ込むときはLINQでListItemの配列を作って、それをAddRangeで流し込むほうがDataSourceに入れてDataBindするよりも楽です。というのも、DataBindだとSelectedを指定するのが非常に難しいというか不可能に近いようなので。こういう色々と中途半端なとこがWeb Formsは嫌ですね。

さて、このボタンを押した実行結果は、「TextBox.Text:あいうえお」「TextBox.ToolTip:」「DropDownList:」になります。TextBox.Textは取り出せてるけど、ToolTipは取り出せてない。DropDownListも全滅。つまるところ、ViewStateをオフにしていると、復元できる(イベントで取り出せる)プロパティと、そうでないプロパティがあります。挙動としては、Web Formsが復元出来るものは自動で復元してくれて、復元できないものは復元してくれない、といった感じです。そして、それだと困るわけです。ToolTipはどうでもいいのですが、DropDownListの値とか取れないと困る。ViewStateがオンなら、全部取得できているのに!やっぱりViewStateはオンにしよう!ではなくて、何とかしましょう。

「復元出来るものは自動で復元してくれる」というけれど、その情報はどこにあるのでしょう。これは、別にWeb Formsだからって特殊なわけでもなんでもなく、Request.Formにあります。

public static class NameValueCollectionExtensions
{
    public static IEnumerable<KeyValuePair<string, string>> AsEnumerable(this NameValueCollection collection)
    {
        return collection.Keys.Cast<string>().Select(x => new KeyValuePair<string, string>(x, collection[x]));
    }
}

Button_Clickのところでブレークポイント張って、Request.Formの中身を覗きましょう。AsEnumerableは独自拡張メソッドです。Request.FormはNameValueCollectionというゴミに格納されていて、Keysは見れるけどValuesが見れないというクソ仕様なので、そこはLINQで何とかしましょうというか、これは多用するのでNameValueCollectionへの拡張メソッドとして定義しておくと捗りますというか、ないと死ぬレベル。

そんなわけで、Formに普通に格納されていることが分かりました。そうそう、ViewStateをオフにしてるはずなのに__VIEWSTATEに値が入ってるぞ!とお怒りかもですが、ほんの少し入ってくるのは仕様なので、そこは我慢しましょう。大した量じゃないので。

では、どうすれば値を取得できるのか、というと

protected void Button_Click(object sender, EventArgs e)
{
    try
    {
        Response.Write("TextBox.Text:" + Request.Form[ToaruTextBox.UniqueID] + "<br />");
        Response.Write("DropDownList:" + Request.Form[ToaruDropDownList.UniqueID]);
        Response.End();
    }
    catch (System.Threading.ThreadAbortException) { }
}

こうです。コントロールのUniqueIDをFormに渡せば良いわけですね。ToolTipとかいうどうでもいいものは取れないので、そういう、HTMLのinputに存在しないものに関しては、PageのViewStateに保存しておけば良いでしょう。私としては、Web Forms使うならinput hiddenよりもthis.ViewState["hoge"]を使うべきだと思います。せっかくある道具ならば、嫌々ながらも有効活用したほうが良いでしょう。

リピーターとチェックボックス

お次は、繰り返しなシチュエーションを考えてみましょう。繰り返しといったらRepeater一択です。それ以外は存在しません。BulletedListすら存在しません。書き出すHTMLを完全にコントロールできるものはRepeater以外存在しません。Web Formsとはなんだったのか。ともかく、Repeaterです。

<asp:Content ContentPlaceHolderID="BodyPlaceHolder" runat="server">
    <form runat="server">
        <asp:Repeater runat="server" ID="ToaruRepeater">
            <ItemTemplate>
                <input runat="server" id="ToaruCheckBox" type="checkbox" value="<%# Container.DataItem %>" />
                チェック 値:<%# Container.DataItem %>
                <br />
            </ItemTemplate>
        </asp:Repeater>
        <asp:Button runat="server" OnClick="Button_Click" Text="ぼたん" />
    </form>
</asp:Content>
public partial class WebForm1 : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (IsPostBack) return;

        ToaruRepeater.DataSource = Enumerable.Range(1, 10);
        DataBind();
    }

    protected void Button_Click(object sender, EventArgs e)
    {
        try
        {
            // CheckされたCheckBoxの値をどう取り出す?
            Response.End();
        }
        catch (System.Threading.ThreadAbortException) { }
    }
}

こんな、まあ簡単な画面があるとします。チェックされたCheckBoxの値をどうやって取り出しましょうか?そうそう、ちなみにですがasp:CheckBoxはvalueが指定できないというゴミ仕様なのでやめておきましょう(Web Formsってそんなのばっか、もうやだよ……。ちなみにコード側のInputAttributesで渡すことは一応できます、一応)。

例によってRequest.Formから取り出すことになるので、まずはデバッガで値を見てやります。

チェックしたチェックボックスの値、ToaruCheckBox,3とToaruCheckBox,8が確認できます。では、どうやって取り出してやろうか。Repeaterの中のコントロールなのでUniqueIDを使うことはできません。ただ、コントロール階層順に$で連結されてる、という法則は見えるわけなので、単純に$でSplitして文字列一致で見てやりましょうか。

protected void Button_Click(object sender, EventArgs e)
{
    try
    {
        var checkedValues = Request.Form.AsEnumerable()
            .Where(x => x.Key.Split('$').Last() == "ToaruCheckBox")
            .Select(x => x.Value);

        // Checked:3, Checked:8
        foreach (var item in checkedValues)
        {
            Response.Write("Checked:" + item + "<br />");
        }
        Response.End();
    }
    catch (System.Threading.ThreadAbortException) { }
}

はい、これで完璧です!なお、ASP.NETのID生成ルールはそれなりに変更が聞くので、詳しくはプログラミングMicrosoft ASP.NET 4 (マイクロソフト公式解説書)でも読めばいいでしょう。

ボタンとCommandArgument

ボタンはOnClickではなくOnCommandを使うと、CommandArgument(とCommandName)を渡せて便利です。簡単な例としては

<asp:Content ContentPlaceHolderID="BodyPlaceHolder" runat="server">
    <form runat="server">
        <asp:Button runat="server" OnCommand="Button_Command" CommandArgument="<%# (int)Fruit.Grape %>" Text="ぶどう!" />
        <asp:Button runat="server" OnCommand="Button_Command" CommandArgument="<%# (int)Fruit.Apple %>" Text="りんご!" />
        <asp:Button runat="server" OnCommand="Button_Command" CommandArgument="<%# (int)Fruit.Orange %>" Text="みかん!" />
    </form>
</asp:Content>
public partial class WebForm1 : System.Web.UI.Page
{
    public enum Fruit
    {
        Grape,
        Apple,
        Orange
    }
    protected void Page_Load(object sender, EventArgs e)
    {
        if (IsPostBack) return;

        DataBind();
    }

    protected void Button_Command(object sender, CommandEventArgs e)
    {
        Response.Write("Clicked:" + (Fruit)int.Parse((string)e.CommandArgument));
    }
}

注意しなければならないのは、aspx上でCommandArgumentに渡すと文字列になるので、enumを渡すときはintにキャストしておくことと、イベント側のCommandEventArgsに渡ってくるときは文字列なのでintにParseしなければならないこと、です。クソ面倒くさいですね、もう少し気が利いてもいいと思うんですが、まあWeb Formsなのでしょうがないと思っておきましょう。

さて、ViewStateがオンならばこれでいいのですが、オフの場合はe.CommandArgumentは常に空文字列になってしまいます。何故か、というと、CommandArgumentはViewStateに乗ってやってくるからです。さて、どうしましょう、というと、解決策は部分的にオンにすることです。

<asp:Content ContentPlaceHolderID="BodyPlaceHolder" runat="server">
    <form runat="server">
        <asp:Button runat="server" ViewStateMode="Enabled" OnCommand="Button_Command" CommandArgument="<%# (int)Fruit.Grape %>" Text="ぶどう!" />
        <asp:Button runat="server" ViewStateMode="Enabled" OnCommand="Button_Command" CommandArgument="<%# (int)Fruit.Apple %>" Text="りんご!" />
        <asp:Button runat="server" ViewStateMode="Enabled" OnCommand="Button_Command" CommandArgument="<%# (int)Fruit.Orange %>" Text="みかん!" />
    </form>
</asp:Content>

ButtonのViewStateModeだけを"Enabled"にすることで、最小限のViewStateで最大限の成果を発揮することができます。ViewStateを嫌うならば、そもそもCommandなんて使わない!になるでしょうけれど、そこまでやってしまうと勿体ない。縁あってWeb Formsを使うわけなのですから、「控えめに」「隠し味」として、ViewStateを有効活用していきましょう。

リピーターとボタン

最後に、リピーターの中にボタンが仕込まれているパターンを見てみましょう。

<asp:Content ContentPlaceHolderID="BodyPlaceHolder" runat="server">
    <form runat="server">
        <asp:Repeater runat="server" ID="ToaruRepeater" ViewStateMode="Enabled">
            <ItemTemplate>
                <%# Container.DataItem %>:<asp:Button runat="server" OnClick="Button_Click" Text="ぼたん!" /><br />
            </ItemTemplate>
        </asp:Repeater>
    </form>
</asp:Content>
public partial class WebForm1 : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (IsPostBack) return;

        ToaruRepeater.DataSource = Enumerable.Range(1, 10).Select(x => new string(Enumerable.Repeat('a', 10000).ToArray()));
        DataBind();
    }

    protected void Button_Click(object sender, EventArgs e)
    {
        try
        {
            Response.Write("Clicked!");
            Response.End();
        }
        catch (System.Threading.ThreadAbortException) { }
    }
}

a(x100000):ボタン という表示結果が得られます。ボタンをクリックするとClicked!と実行されて欲しいわけですが、ViewStateがオフだとうんともすんとも言いません。何故か、というと、イベントの選択もまたViewStateに乗ってくるからです。解決策はRepeaterのViewStateModeをEnabledにすること、です。

しかし、単純にRepeaterのViewStateModeをEnabledにしただけだと、それ以下の全てのViewStateがオンになってしまいます。どういうことかというと、ViewStateを見てみると、この場合の結果は「133656文字」もあります!どれだけデカいんだよ!なぜかというとaaaaaa...(x10000) x 10がViewStateに乗っかってしまったからです。ただたんにボタンクリックできればいいだけなのに!じゃあ、どうするか、というと、ViewStateのオンオフを入れ子にしてRepeaterだけをオンにします。

<asp:Content ContentPlaceHolderID="BodyPlaceHolder" runat="server">
    <form runat="server">
    <asp:Repeater runat="server" ID="ToaruRepeater" ViewStateMode="Enabled">
        <ItemTemplate>
            <asp:PlaceHolder runat="server" ViewStateMode="Disabled">
                <%# Container.DataItem %>:<asp:Button runat="server" OnClick="Button_Click" Text="ぼたん!" /><br />
            </asp:PlaceHolder>
        </ItemTemplate>
    </asp:Repeater>
    </form>
</asp:Content>

これで解決。クソ面倒くさくて回りっくどくてイライラしますが、"そういうもの"だと思うしかないです。

Web Formsの良さをスポイルしてまでWeb Forms使いたい?

使いたくないです。それでもやらなきゃいけない時はあるんですDataSet死ね。

(ところで関係なく)async event

Visual Studio 2012 RC出ました!neue cc - Visual Studio 11の非同期(”C#, ASP.NET, Web Forms, MVC”)で非同期系について特集しましたが、Web FormsではRegisterAsyncTask(new PageAsyncTask)しなきゃならなくて面倒くさい死ねという感じでしたが、ようやくイベントにasyncをつけるだけでよくなりました!

protected async void Button_Click(object sender, EventArgs e)
{
    await Task.Delay(TimeSpan.FromSeconds(3));
    try
    {
        Response.Write("hoge");
        Response.End();
    }
    catch (System.Threading.ThreadAbortException) { }
}

正式リリース前に対応してくれて本当に良かった。これでWeb Formsでももう少し戦える……、戦いたくないけど。

まとめ

世の中にはUnobtrusive JavaScriptという言葉がありますが、そのように、私としてもUnobtrusive ViewStateを唱えたい。控えめに。とにかく控えめに。ほとんどないも同然なぐらいに。隠し味として使うのが、一番良いわけです。今までのWeb Formsはデフォルトオンで化学調味料をドバドバと投げ込んでいました。そんなものは食えたものじゃあありません。化学調味料を使うなら、超絶控えめに、ほんの少しでいいんです。そうすれば、革命的に美味しくなるのですから。それが正しい化学調味料の使い方。

そして、理想を言えば化学調味料はゼロがいいんですけれどね。ゼロにしたければWeb Formsはやめましょう。それ以外の答えはない。Web Formsを使う以上は正しく向き合うことが大事。

ViewStateをドバドバ使うことがWeb Formsの良さだというのも半分は事実ですが、半分はNOですね。ていうか馬鹿でしょ。そういう発想に未来はないし脳みそイカれてると思いますよ。DataSetを褒め称えていた狂った時代の発想なので、腐った部位はとっとと切り落としましょう。

さて、そうして控えめにしたWeb Formsですけれど、これはこれでそれなりに良いとは思います。最小限にしたとしても、依然としてパーツ配置してOnClickでほいほい、という楽ちんさは健在ですし、カスタムコントロールによるモジュール化というのは悪くない。絶賛はしませんが、悪くないとは思います、そこまで悪しざまにいうものでもない。でも、まあ、もはや時代じゃあないでしょうね。もう、さようなら。

と、まあ割とキツめにWeb Forms(やDataSet)にあたるのは日々苦しめられているからなので、罵詈雑言多いのは勘弁してね!別に現状がダメでも未来に良くなるとかならいいんですが、明らかに未来がないので、なんというかもうねえ、という。Web Formsも、これはこれで面白い仕組みだし、まっとうに進む道もあったとは思うんですが、舵取りにしくったと思います。結果的にこの閉塞感漂う現状と、明らかにリソース割かれてない感があるので、未来はないですね、残念ながら。しかし、最後の徒花としてやれるだけはやるのさぁ。

配列とGetEnumetorのお話

LINQ書いていますか?LINQでデータベース活用とか見出しにありますが、データベース活用といいつつ、その内実は100% LINQ to Objectsです。じゃあデータベースに何で問い合わせやってるの?というと(禁則事項です)。さて、そんなわけで毎日LINQ書いているわけですが、それとは全く関係なく、配列が結構困ったちゃん。例えば以下のような、配列を包んだコレクションを提供したいとします。

public class WrappedCollection<T>
{
    T[] source;

    public WrappedCollection(T[] innerSource)
    {
        this.source = innerSource;
    }
}

で、T[]なsourceを元にメソッドを幾つか提供する、と。それ自体はないこともないと思われます。さて、Collectionを名乗っているので、IEnumerable<T>であってほしいですよね?

なぜだ、って、配列のGetEnumeratorの戻り値はIEnumeratorなのです。IEnumerator<T>ではなくて。マジで!マジで。さて、どうしようかしら、と。foreach(var item in source) yield return item; をすれば解決ですが、そんなダサいことはやりたくない。正解は、AsEnumerableです。

public class WrappedCollection<T> : IEnumerable<T>
{
    T[] source;

    public WrappedCollection(T[] innerSource)
    {
        this.source = innerSource;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return source.AsEnumerable().GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

これだけで解決。やったね、AsEnumerableは偉大で奥深いなあ。さて、以前にDeep Dive AsEnumerableで書きましたが、AsEnumerableの実態はただのキャストなので

return ((IEnumerable<T>)source).GetEnumerator();

でもOKです。

さて、それらの中身ですが

var source = Enumerable.Range(1, 10).ToArray();

var e1 = source.GetEnumerator();
var e2 = source.AsEnumerable().GetEnumerator();

// System.Array+SZArrayEnumerator
Console.WriteLine(e1.GetType());
// System.SZArrayHelper+SZGenericArrayEnumerator`1[System.Int32]
Console.WriteLine(e2.GetType());

といった具合に、型が違うと渡ってくるEnumeratorも違うようですね。これ自体は別にスペシャルな機能ではなく明示的なインターフェイスの実装をした時の挙動、ではありますが、まあ配列周りはそもそもに色々とややこしいですからね。私みたいなゆとりなんて、SZって何だよクソが(single-dimension zero-baseの意味だそうで)とか思ってしまいます。

まとめ

IEnumerable<T>じゃないコレクションは逝ってよし。つまりMatchCollectionは何で.NET 4.5になっても手を加えてくれないんだよぅううううぅぅぅ。

Prev | | Next

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

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

X:@neuecc GitHub:neuecc

Archive