ソーシャルゲームとわたくし

最近のWeb業界の風潮にならって、ちゃんとロクロ回しました!

猫も杓子もみんなソーシャルゲーム業界に行くんだなあ、とかポケーッと眺めていましたが、まさか私も行くとは全然思ってもいませんでした。というわけで、1月から転職したと書きましたけれど、株式会社gloopsで働いています。転職理由とかは、インタビューの通りです。C#を世間(といっても、技術系の人に、ですね)に強くアピールするにはどうすればいいのか、といったら、一番大事なのはそれで書かれたメジャーなアプリケーションなのです。PerlではてなやMixi、livedoorなどを思い浮かべる、RubyでCookpadを、ScalaでFoursquareやTwitterを、そういった憧れにも似た気持ちを浮かべさせるようなアプリケーションがなければいけなくて。

Stack OverflowはC#, ASP.NET MVCだし、TIOBEのプログラミング言語ランキングでは三位など、海外でのC#の地位は十分に高い。のですが、国内ではそれと比べれば全くもってない。日本で誰もが知る会社の誰もが知るアプリケーション、それがC#で書かれている。そういう状態にならなければ、日本で強く普及は無理だな、と。

ギョーム系アプリでは十分に強いじゃん、とかそうじゃなくて、私としてはもっと学生とかも憧れてガンガン使う感じであって欲しいんですよ。Visual Studioは無料版ありますし、学生向けの支援(DreamSparkなど)もやってはいますが、あまりリーチできてないのではないかなあ、って。そういうのって内からやりたいって気持ちが湧いてきて初めて支援があって嬉しい!になるわけで。

まあその領域だったら、やっぱゲームですよゲーム!なんというか、Unityブームのほうがずっと請求してるかもなのですかねー。というわけで第77回codeseek勉強会&第17回日本C#ユーザー会 勉強会 : C#とゲームでは、C#とゲーム全般を取り扱って、そのなかで弊社CTOの池田もソーシャルゲーム枠でセッション持ちますので、残席まだありますので是非是非どうぞ(宣伝)

ゲームもいいのですが、デスクトップアプリケーションもいいし(最近はあんまし流行らないですって?そうかもですがー)、モバイルアプリもいいし(Windows Phone 7よりも、MonoTouchやMono for Androidのほうが受けますな)、そして、ウェブアプリもいい。C#の持つ強みや範囲というのは、本当に、もっともっと知られて欲しいなって。

そんなようなことは入社初日にも言ったりなどしていて、今も当然変わってませんし、3ヶ月働いてきて、実現できる会社であると実感しています。2012 グループス MLB開幕戦の冠協賛や、最近はCMも増えてきたりなど、露出も増えてきて、勢いありますね。その勢いを止めない、加速させるためにも、まだまだ人が必要というわけで、求人バナーもかなり見かけてるんじゃないかと思います。というわけで弊社ではエンジニアを絶賛募集中なのでC#で書きたい!という人は是非是非お願いします(宣伝)

あと、私自身の目的としてはもう一つあって、日本ローカルだけじゃなく世界にも通用する技術力を掲示したいという欲求があります。その面でも、世界に向けても前進している(gloops International CEO冨田由紀治氏インタビュー - GAME Watch)のは、マッチしていました。とはいえ、まずは日本、です。そもそも全然半人前だと痛感する毎日で寝言は寝てから言え状態なので、日々鍛錬ですね。

ソーシャルゲーム

このサイトの前身はゲーム攻略サイト(Xbox系)で、2002年からです(このサイトのアレゲな配色はその頃から全く変わってないから、というのが大きな理由です)。また、特に好きなGeometry Warsなんて世界ランキングでもかなり上位だったりニコ動にプレイ動画を投稿したりする程度には、一応そこそこハードなゲーマーを自称していたのですが、最近はめっきりゲームとは遠ざかってしまいました。あうあう。完全にプログラミング系サイトですしねー。

というわけで、元ゲーマーとでもしませうか。で、ソーシャルゲーム。ゲーマーといったらソーシャルゲームは割と忌み嫌うという感じの!ふむ。まあでも、ほら、最近はアイドルマスター シンデレラガールズが(色々な意味で)話題だったり、それはそれで独特に面白さってのはあるんですね。いや、モゲマス面白いですよ、ガチャ地獄とか除いても普通に。必ず人と繋がり合う(それが衣装泥棒であっても)、それも緩やかに非同期に(MMORPGや対戦系は同期的ですから)というのは、独特なものがあると思います。

正直いって、まだ完全に面白さには繋がってないとは思いますが(特にゲーマー向けには)、このシンプルで非常に限られた中からゲーム性(と○○)を引き出すチャンスは転がっている、かもしれません。ずっとガラケー向けに貧相な画面とボタン連打だけで変わらない、わけでは、ない。スマートフォンの時代は来ているし、HTML5の流れだってあるし、それはソーシャルゲームだって同じなのです。

フロントエンドはモバイル向けというのを考慮しながらもリッチしなければならないし、バックエンドはハイパフォーマンスに耐えなければならない。課題は大量にありますが、だからこそ技術者としては挑戦しがいのある面白いテーマが山のように転がっています。例えばC# 5.0が非同期でハイパフォーマンスといったって、別にそんなのそこまで必要じゃないしなー、で終わってしまう状況もいっぱいあると思うのです。でも、弊社では、今すぐにでも必要なのだというぐらいに切羽詰ってる。C#の能力をフルに使いきることが求められる。これは楽しいですね?はい、楽しいです。

会社員ですからー

インタビュー記事が求人記事なので、会社員として宣伝しました!なので基本的にイイ事しか言いませんが、勿論イクナイ面もそれなりにあります。例えば最近の私の記事を見ると某テクノロジーをやたらDisってますが、なんでなんでしょうかねー、ふふり。とはいえ、不満に思うなら自分でぶち壊して再構築すればいいし、それが許される(勿論ちゃんと能力を証明したうえで)環境だとは思います。スキルある人が何の制約もなく存分に腕をふるえるのなら、素敵な話ではないでしょうか。

ちなみに私のスキルはC#/LINQに偏っていてウェブとかASP.NETの知識は並なので、毎日勉強です、人はそれを付け焼刃とも言う。

Visual Studio 11の非同期("C#, ASP.NET, Web Forms, MVC")

世の中ひどぅーきひどぅーきと騒ぐばかりで、猫も杓子もNode.js。でもですね、【デブサミ2012】16-A-5 レポート ソーシャルアプリケーションにおけるNode.jsの活かし方(1/2):CodeZineなんかを見ても、そこで独自に作りこんでる例外処理だの非同期フロー管理だのは、そりゃあ必要ですよね、まずはそこから始めるのは当然ですよね、と思いつつC#は最初から備えているんですよね。むしろ色々とC#のほうが、とか思ったりするわけですが(勿論Node.jsのほうがGoodなものもありますが)、こんなところで嘆いていても始まらないのでC#流の非同期の活かし方を見ていきましょうか。

HttpTaskAsyncHandler

ASP.NETの非同期ハンドラはIHttpAsyncHandlerなわけですが、VS11ではそれをTask(つまりC# 5.0 async/await)で扱いやすくした基底クラス、HttpTaskAsyncHandlerが用意されています。例えばTwitterの検索を叩いて返すだけどのものは以下のようになります。

public class TwitterSearchHandler : HttpTaskAsyncHandler
{
    public async override Task ProcessRequestAsync(HttpContext context)
    {
        var term = context.Request.QueryString["q"];
        var json = await new HttpClient().GetStringAsync("http://search.twitter.com/search.json?q=" + term);

        context.Response.ContentType = "application/json";
        context.Response.Write(json);
    }
}

普通と違うのはasyncとawaitだけなので、特に混乱もなく同期→非同期に乗り換えられると思います。非常に簡単。

HttpClientも.NET 4.5からの新顔で、WebClientの後継的な位置付けでしょうか。細かいコントロールも可能で、かつ、WebRequestよりも簡単で、非同期にもきっちりマッチしている。というかHttpClientには同期的なメソッドは用意されていません。これからの非同期世代に完全準拠した新しいクラスということですね。

そして、テスト用のサーバー立てるのも非常に簡単で。Visual Studioで新規で空のASP.NETサイトプロジェクトを作って、↑のハンドラ足して、Ctrl + F5すればIIS Expressが立ち上がって、もうそれだけでOKなわけですよ。超簡単なわけですよ、マジでマジで。

こないだ、RIA アーキテクチャー研究会 第3回でのセッションではそうして作ったHttpTaskAsyncHandlerで、 context.Response.StatusCode = 404 にしてエラーを返した状態を再現したりしながらデモしていました。

今回はTaskを中心にしましたが、Rxを中心にしたものをSilverlightを囲む会in東京#6で3/31に話す予定なので、まだ募集中なので是非来て下さい。また、Rx v2.0に関してはReactive Extensions v2.0 Beta available now! - Reactive Extensions Team Blog - Site Home - MSDN Blogsで超詳細に書かれていますね。私もちょいちょいと書きたいことは溜まってるのですが中々にぐぬぬぬ。

非同期ページ

今更Web Formsとか超どうでもいいって感じが世界全体に漂ってるし真面目に色々と腐ってると本気で思うしDataSetとWeb Formsは今となっては.NET三大汚点の筆頭かなとか思ったり思わなかったり適当に言ったり呪詛を吐いたり、もう色々アレなのですが、それでも現実とは戦わなければならないのです!

というわけでVS11のASP.NET Web Formsの非同期の強化でも見てみましょう。C# 5.0でasync/awaitが入るのでASP.NET MVCのほうは非同期コントローラーでヒャッホイなのですがWeb Formsも一応対応してきました、一応ね、一応。

// Web.config
<appSettings>
  <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
</appSettings> 
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="~/WebForm1.aspx.cs" Inherits="WebApplication8.WebForm1"
    Async="true" ViewStateMode="Disabled" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Async Test</title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:TextBox ID="WordTextBox" runat="server" />
    <asp:Button ID="SearchButton" runat="server" Text="Button" OnClick="SearchButton_Click" />
    <asp:Repeater runat="server" ID="TwitterStatuses" ItemType="dynamic">
        <ItemTemplate>
            <p>
                <asp:Label runat="server" Text="<%#: Item.from_user %>" /><br />
                <asp:Label runat="server" Text="<%#: Item.text %>" />
            </p>
        </ItemTemplate>
    </asp:Repeater>
    </form>
</body>
</html>
// namespace WebApplication8
public partial class WebForm1 : System.Web.UI.Page
{
    protected void SearchButton_Click(object sender, EventArgs e)
    {
        var task = new PageAsyncTask(async () =>
        {
            var word = WordTextBox.Text;
            using (var stream = await new HttpClient().GetStreamAsync("http://search.twitter.com/search.json?q=" + word))
            {
                var json = System.Json.JsonObject.Load(stream);
                TwitterStatuses.DataSource = json["results"];
            }
            DataBind();
        });

        RegisterAsyncTask(task);
    }
}

非同期ページの利用には Async="true" 属性をつける必要があります。.NET 4.0まではつけていない場合は、同期的に動作するようになっていたのですが、.NET 4.5からはエラーになるように挙動が変更されています。また、PageAsyncTaskを利用する場合はWeb.configにUseTaskFriendlySynchronizationContext = true する必要もあるっぽいです。

これ自体はテキストボックスに検索語を入れてボタンを押すとひどぅーきでTwitter検索して表示する、というだけのDoudemoii代物です。PageAsyncTaskが引数にTaskを受け入れるようになったので、そこでasyncなラムダ式を突っ込んでやればいい、というわけで、まあまあ簡単と言えなくもなく仕上がっています。理想的には/直感的にはasync void SearchButton_Clickと書けるようになるべきなのですが、そうはいかないようです、残念。

JSONは.NET 4.5からお目見えのSystem.Jsonを使いました。これ、AsDynamic()とするとdynamicで扱えるのでサクサクッと使えて便利です。また、そのdynamicとして使える性質を活かして、dynamicのままバインドしてみました(AsDynamicはコード上dynamicにキャストするというだけで、JsonValueはそのもの自身がdynamic = IDynamicMetaObjectProviderなのです)。System.JsonはNuGet - System.Jsonにもあるので、.NET 4ではそれを使えばいいでしょう。DynamicJsonはお払い箱で。

それとRepeaterのItemType="dynamic"。これでItem.from_userといったように、dynamicに使えるようになっています。匿名型をバインドしたい時なんかも、同じようにItemType="dynamic"にしてしまうといいかな、と思ったんですが、それは出来ませんでした。あともう一歩、気を利かせてくれても良かったですねえ。

まあ、VS11からは、念願のバインディング式の中でIntelliSenseが効くようになっていて、それはRepeaterのItemTypeも例外ではないので、ちゃんと型作ってあげるのも良いとは思います。あと%:でHtmlEncodeもしてくれますのも良いところ。

ViewStateMode="Disabled"で無駄なViewStateは生成しないようにするのも大事。これは.NET 4.0からですね。EnableViewStateとは別物という紛らわしさが残っているのも、まあなんともかんとも。ところでPageのViewStateModeをDisableにしてしまうと、this.ViewState[]が使えなくなってしまうので、マスターページからの、asp:Contentにしかけたほうがいいかもです。

EventHandlerTaskAsyncHelper

ASP.NETの非同期関連はMSDNマガジンのWickedCode: ASP.NET の非同期プログラミングを使ったスケール変換可能なアプリケーションにまとまっていますが、そこにあるとおり非同期ページの実現方法にはもうひとつ、AddOnPreRenderCompleteAsyncを使う方法があります。それにもTask用のやり方がありますので、見てみましょう。

var helper = new EventHandlerTaskAsyncHelper(async (_, __) =>
{
    var word = WordTextBox.Text;
    using (var stream = await new HttpClient().GetStreamAsync("http://search.twitter.com/search.json?q=" + word))
    {
        var json = System.Json.JsonObject.Load(stream);
        TwitterStatuses.DataSource = json["results"];
    }
    DataBind();
});

AddOnPreRenderCompleteAsync(helper.BeginEventHandler, helper.EndEventHandler);

EventHandlerTaskAsyncHelperを作り、それのBeginとEndをAddOnPreRenderCompleteAsyncに渡してあげます。ちょっとPageAsyncTaskより面倒ですね。まあ、でも、どちらでもいいでしょう。大した違いはありません。二つやり方があるとどちらにすればいいのかと迷ってしまうのが良くないところなんですよねえ、しかもどちらも似たようなものだと……。

非同期モジュール

Moduleについても見てみましょう。感覚的にはAddOnPreRenderCompleteAsyncと一緒で、EventHandlerTaskAsyncHelperを作り、追加したいイベントにBeginとEndを渡します。

public class MyModule : IHttpModule
{
    public void Init(HttpApplication application)
    {
        var helper = new EventHandlerTaskAsyncHelper(async (sender, e) =>
        {
            var app = (HttpApplication)sender;
            var path = app.Server.MapPath("~/log.txt");

            using (var fs = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read, 4096, useAsync: true))
            using (var sw = new StreamWriter(fs, Encoding.UTF8))
            {
                await sw.WriteLineAsync("Request:" + DateTime.Now);
            }
        });

        application.AddOnBeginRequestAsync(helper.BeginEventHandler, helper.EndEventHandler);
    }

    public void Dispose() { }
}

AddOnXxxAsyncは沢山あるので、追加したいイベントを選べばいいでしょう。また、非同期でファイルを扱いたい時は、useAsync: trueにするのが大事です。デフォルトはfalseになっているので、Begin-Endをしても非同期にならない(というかスレッドプールを使った挙動になってしまう)そうです(と、プログラミング.NET Frameworkに書いてあった)。

非同期コントローラー

一応ASP.NET MVCでも見てみましょうか。TwitterのPublicTimelineを表示するだけのものを(テキストボックスすら作るのが面倒になってきた)

public class PublicTimelineController : AsyncController
{
    public async Task<ActionResult> Index()
    {
        using (var stream = await new HttpClient().GetStreamAsync("https://twitter.com/statuses/public_timeline.json"))
        {
            var json = System.Json.JsonObject.Load(stream);
            return View(json);
        }
    }
}
<!DOCTYPE html>
<html>
<head></head>
<body>
    @foreach (var item in Model)
    {
        <p>
            @item.user.screen_name
            <br />
            @item.text
        </p>
    }
</body>
</html>

AsyncControllerの自然さと、きゃーRazor最高ー抱いてー。

まとめ

HttpTaskAsyncHandlerにせよEventHandlerTaskAsyncHelperにせよ、中身は割とシンプルにTaskでラップしただけなので、それを自前で用意すればTask自体は.NET 4.0に存在するので、async/awaitは使えませんがそれなりに簡単に書けるようにはなります。とりあえず私はWeb Forms用のものを仕事で使うために用意しました。コードは会社で書いたものなので上げられませんが!というほど大したものでもないので上げちゃってもいいんですが上げません!Web Formsにはとっととお亡くなりになってもらいたいので。延命措置禁止。

Web Formsだって悪くないものだ、全力で頑張ればほら、こんなに出来るじゃないか、ということは容易い、ことはまったくなく全力なわけですが、しかし可能ではあるんですね、モバイル対応だろうがハイパフォーマンスサイトだろうが。きっとたぶん。でもね、なんかもうIE6にも対応しつつHTML5サイトです、とかやるぐらいに不毛感漂ってるし、その労力は別のとこに向けたいですよね、っていうか別のとこに向けばどれだけ幸せになれるだろうか、と思ってしまうのです。

考えてみると、こうもうぇぶけーな話を書くのも初めてな気がする。近頃はお仕事がそっち方面なので、出せる範囲でちょいちょい出してこうかと思います。とにかく結論としてはWeb Formsちゃんは、もう沢山頑張ったと思うのでそろそろ逝ってもらって構いません。

LINQのWhereやSelect連打のパフォーマンス最適化について

Where連打していますか?それともパフォーマンスの悪化を心配して&&連結にしていますか?LINQの仕組み&遅延評価の正しい基礎知識 - @ITではWhere+Selectに対して

「WhereSelectEnumerableIterator」となっていて、名前のとおり、WhereとSelectが統合されていることです。これは、「Where」->「Select」が頻出パターンなので、それらを統合することでパフォーマンスを向上させるためでしょう。

と書きましたが、では連打の場合はどうなっているでしょうか。見てみましょう。

var seq1 = Enumerable.Range(1, 10)
    .Where(x => x % 2 == 0)
    .Where(x => x % 3 == 0);

// どうでもいいんですが、これはVisual Studio 11 Betaです。VS11最高ですよ!

@ITの記事では、sourceに格納されて内包した形の連鎖となっている、と書きました。しかしseq1のsourceはRangeIteratorで、Where連打のはずなのに、すぐ上の階層が元ソースとなっています。そして、predicateの名前がCombinePredicates。はい、その通りで、2つの条件式が連結されています。確認してみましょう。

var pred = (Func<int, bool>)seq1.GetType().GetField("predicate", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(seq1);

Console.WriteLine(pred(2)); // false
Console.WriteLine(pred(3)); // false
Console.WriteLine(pred(6)); // true

というわけで、Where連打はpredicateが連結されて一つのWhereに最適化されることが確認できました。LinqとCountの効率でICollectionやIListの場合の特別扱いなケースがあることを紹介しましたが、Whereに関しても同様な特別扱いが発生するというわけです。

Selectの場合

Whereの他にSelectの場合も、同じような最適化を行ってくれます。

var seq2 = Enumerable.Range(1, 10)
    .Select(x => x * 2)
    .Select(x => x + 10);

var selector = (Func<int, int>)seq2.GetType().GetField("selector", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(seq2);

Console.WriteLine(selector(2)); // 2 * 2 + 10 = 14
Console.WriteLine(selector(5)); // 5 * 2 + 10 = 20

sourceのすぐ上がRangeIteratorで、selectorにCombineSelectorsとして格納されていました。なお、型名であるWhereSelectEnumerableIteratorのとおり、現在はpredicateはnullですが、前段にWhereを書けばpredicateに格納されて、やはりWhere+Selectの最適化となります。では、後段にWhereを書いた場合は……?

最適化されない場合

Where+SelectとSelect+Whereは異なるものです。見てみましょう。

var whereSelect = Enumerable.Range(1, 10)
    .Where(x => x % 2 == 0)
    .Select(x => x * 2);

var selectWhere = Enumerable.Range(1, 10)
    .Select(x => x * 2)
    .Where(x => x % 2 == 0);

Where+SelectはWhereSelectEnumerableIteratorのpredicateとselectorにそれぞれデリゲートが格納され、ひとまとめに最適化されていますが、Select+WhereはsourceがRangeIteratorではなくWhereSelectEnumerableIteratorであるように、普通に階層の内包構造となっています。Selectの後にWhereは最適化されません。まあ、そりゃ値が変形されているのだからpredicateがひとまとまりになるわけがなく、当たり前ではあります。

次にインデックスが使えるオーバーロードのケースを見てみましょう。

var whereIndex = Enumerable.Range(1, 10)
    .Where(x => x % 2 == 0)
    .Where((x, i) => i % 2 == 0);

var selectIndex = Enumerable.Range(1, 10)
    .Select(x => x * 2)
    .Select((x, i) => i * 2);

// GetEnumeratorしないとpredicate/selectorとsourceがnullです
// これはyield returnによる生成なためです
whereIndex.GetEnumerator();
selectIndex.GetEnumerator();

これもひとまとめにしようにも、しようがないので、当然といえば当然ですね。

IQueryableの場合

IQueryableだとどうなのか、というと……

// LINQ to SQL - AdventureWorks sample
var ctx = new AdventureWorksDataContext();
var query = from model in ctx.ProductModel
            where model.Name == "hoge"
            where model.ProductModelID == 100
            select model.Instructions;

Console.WriteLine(query);
// 結果
SELECT [t0].[Instructions]
FROM [Production].[ProductModel] AS [t0]
WHERE ([t0].[ProductModelID] = @p0) AND ([t0].[Name] = @p1)

というわけで、LINQ to SQLはand連結されますね。ここで注意なのが、どういう挙動を取るのかは全てクエリプロバイダの解釈次第です。例えばLINQ to Twitterはwhere連打ではダメで、&&で連結しなければなりません。

Reactive Extensionsの場合

Reactive Extensionsの場合も見てみましょうか。Rx-Main(1.0.11226)では、というと

var rx = Observable.Range(1, 10)
    .Where(x => x % 2 == 0)
    .Where(x => x % 3 == 0);

さっぱりワケワカメですが、とりあえずひとまとめになってないのでは感でしょうか。それにしても本当にワケワカメ。次にRx_Experimental-Main(1.1.11111)は、というと

var pred = (Func<int, bool>)rx.GetType().GetField("_predicate", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(rx);

Console.WriteLine(pred(2)); // false
Console.WriteLine(pred(3)); // false
Console.WriteLine(pred(6)); // true

_predicate発見!Experimental版では挙動が改善され、ひとまとめにされているようです。IDisposable<Generate>は、Rangeの生成がGenerateメソッドによってなされているからですね。しかし、やはり読み取りにくい。

Rx v2

3/5にReactive Extensions (Rx) v2.0 Betaの配布がスタートしています。NuGetではInstall-Package Rx-Main -Preで配布されていますね。改善内容は後日詳しくということでまだ詳しくは出てないのですが、v2というだけあって中身はガラッと変わっています。とりあえず、見てみましょう。

もちろん、predicateはひとまとめにされているのですが、それだけじゃなくて、とにかく見やすい、分かりやすい。しかし、ところどころ変ですね、Observαble(aがアルファ)だのΩだの。v2はソースのキチガイ度が跳ね上がっているのでILSpyとかで覗いちゃえる人は一度見ちゃうといいと思います、頭おかしい。あと、C#でのプログラミング的な小技も効いてたりして、テクニックの学習にもとても良い。

スタックトレースへの影響

このコードのクリアさはスタックトレースにも良い影響を与えています。まず、Rx v1で見てみると

try
{
    Observable.Range(1, 10)
        .Where(x => x % 2 == 0)
        .Take(10)
        .Timestamp()
        .Subscribe(_ => { throw new Exception(); });
}
catch (Exception ex)
{
    Console.WriteLine(ex.StackTrace);
}
場所 ConsoleApplication9.Program.<Main>b__1(Timestamped`1 _) 場所 c:\Users\ne
場所 System.Reactive.AnonymousObserver`1.Next(T value)
場所 System.Reactive.AbstractObserver`1.OnNext(T value)
場所 System.Reactive.AnonymousObservable`1.AutoDetachObserver.Next(T value)
場所 System.Reactive.AbstractObserver`1.OnNext(T value)
場所 System.Reactive.Linq.Observable.<>c__DisplayClass408`2.<>c__DisplayClass40a.<Select>b__407(TSource x)
場所 System.Reactive.AnonymousObserver`1.Next(T value)
場所 System.Reactive.AbstractObserver`1.OnNext(T value)
場所 System.Reactive.AnonymousObservable`1.AutoDetachObserver.Next(T value)
場所 System.Reactive.AbstractObserver`1.OnNext(T value)
場所 System.Reactive.Linq.Observable.<>c__DisplayClass43e`1.<>c__DisplayClass440.<Take_>b__43d(TSource x)
// 以下略

これは酷い。こんなの見ても何一つ分かりはしません。では、Rx v2で試してみると

場所 ConsoleApplication10.Program.<Main>b__1(Timestamped`1 _) 場所 c:\Users\n
場所 System.Reactive.AnonymousObserver`1.Next(T value)
場所 System.Reactive.ObserverBase`1.OnNext(T value)
場所 System.Reactive.Linq.Observαble.Timestamp`1._.OnNext(TSource value)
場所 System.Reactive.Linq.Observαble.Take`1._.OnNext(TSource value)
場所 System.Reactive.Linq.Observαble.Where`1._.OnNext(TSource value)
場所 System.Reactive.Linq.Observαble.Range._.LoopRec(Int32 i, Action`1 recurse)
場所 System.Reactive.Concurrency.Scheduler.<>c__DisplayClass3a`1.<InvokeRec1>b__37(TState state1)
// 以下略

めっちゃよく分かる。Timestamp->Take->Where->Rangeという遡りがしっかり見える。何て素晴らしいんだ!

匿名 vs 有名

さて、どういうことかというと、これ、neue cc - Rx(Reactive Extensions)を自前簡易再実装するで紹介したような、ラムダ式をぶん投げてその場で匿名のクラスを作るAnonymousパターンをやめたんですね。で、代わりに名前付きのクラスを立ててる。だから分かりやすい。

これ、uupaaさんが仰ってるナビ子記法←ググッた先の本人のスライドが、Handsoutがサービス終了で見れないので、紹介のある記事にリンクします-などにも近いところがあるかなあ、と。

ただやっぱ書くのにはコスト高というか匿名で書けることの良さを殺してしまうので、ライブラリサイドだったら検討する、アプリケーションサイドだったらやらない、になってしまうかなあ。ライブラリサイドであってもかなり手間なので、よほど余裕あるとかでないとやらない、かなあ。JavaScriptならともかくC#では、特に……。

Rx v2についてもう少し

詳しい話は詳細が出てから、と思いますが(と言いながらRxJSの話も結局書いてないので、あうあう)、とりあえずObservableへの拡張メソッド郡はExperimentalから変化は特にありません。ただ、Experimentalは既にStableとはかなり違っているので、Stableしか追っかけてない人は、かなり目新しいものを見かけることができると思います。

内部実装は見たとおりガラッと変わって、スタックトレースも見やすくなった、などなどなわけですが、それとあわせてパフォーマンスも相当上がっています。v1で基本的な部分を固めたので、v2ではそういった周辺部分に本気で取り組みだした、ということですね。

まとめ

LINQは細かいところまで配慮が行き届いていて本当に素晴らしいですね。というわけで平然とWhereの連打かましましょう。私もつい昨日にWhere6連打かましたりしてました。

linq.js - LINQ for JavaScriptはさすがにここまではやってないんですが、いずれかはやりたいですね。その前にやらなきゃならないことがありすぎて当面はないですけれど。うーん、なんかもう色々やることありすぎて、かつそれなりに忙しくて、頭が爆発しそうです。はっきしいってヤバい。で、こうしてヤバくなると、硬直しちゃって余計に何もできなくなったり、唐突にこうして息抜き記事を書き出したり、うみみぅ。まあともかく、がんばろふ。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

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

X:@neuecc GitHub:neuecc

Archive