Archive - C#

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

とあるRoslynではないC# Parser、NRefactoryの紹介

ロッズリーン、はっじまらないよ~。というわけでMicrosoft “Roslyn” CTP、Compiler as a Service。NuGet - Roslynでも手に入るので、サンプル類やC# Interactiveとかはなしで、とりあえずScriptingやCompilerを触ってみたい、ということなら、お手軽です。しかし、まあ未実装も少なくなく、まだまだ先は長そうな雰囲気ではある。今すぐ欲しいのに!切実にC# Parserが!というわけで、今日はその良き代替となる(かもしれない)NRefactoryを紹介します。

NRefactoryはNuGet - NRefactoryからも入ります。verは5.0.0.4、「This is an alpha release. Expect bugs and breaking changes in the future.」とのことで、こちらもまだまだこれからのよう(MonoDevelopの新しいC#エディタで使われる予定、だそうです)。とりあえずNuGetで参照してみませう。

参照するとMono.Cecilが入ったり名前空間にMono.CSharpがあったりと、全体的にMonoのCSharp Compilerやその周辺が使われているふいんき。Mono.CSharpは単体でもついこないだNuGet - Mono.CSharpで入れられるようになりましたが、そのまんまだと、なんというかどう使っていいか分からないというか、はいEvalできた、さて、はて?みたいになってしまって。そのへん、NRefactoryはゆるふわで、結構すぐに使い方分かります。とりあえず使ってみませう。

using System;
using System.IO;
using System.Linq;
using ICSharpCode.NRefactory.CSharp;
 
class Program
{
    static void Main(string[] args)
    {
        var code = File.ReadAllText(@"../../Program.cs");
        var parser = new CSharpParser();
 
        var root = parser.Parse(code, "");
 
        var program = root.Descendants.OfType<ICSharpCode.NRefactory.CSharp.TypeDeclaration>().First();
        program.Name = "Hogegram";
 
        Console.WriteLine(root.ToSourceString());
    }
}
 
public static class CompilationUnitExtensions
{
    static readonly CSharpFormattingOptions DefaultOptions = new CSharpFormattingOptions()
    {
        // TODO:130のboolを自分の気にいるようなOption(というかVSのデフォ)に近づける
    };
 
    public static string ToSourceString(this CompilationUnit compilationUnit, int indentation = 4, string indentationString = " ")
    {
        return ToSourceString(compilationUnit, DefaultOptions, indentation, indentationString);
    }
 
    public static string ToSourceString(this CompilationUnit compilationUnit, CSharpFormattingOptions options, int indentation = 4, string indentationString = " ")
    {
        using (var sw = new StringWriter())
        {
            var formatter = new TextWriterOutputFormatter(sw)
            {
                Indentation = indentation,
                IndentationString = indentationString
            };
 
            var visitor = new CSharpOutputVisitor(formatter, options);
            compilationUnit.AcceptVisitor(visitor);
            return sw.ToString();
        }
    }
}

Program.csを読み込んで、クラス名をHogegramに変更したのを出力する、というだけのものです。今のところ整形した文字列化にはCSharpOutputVisitorを作って、AcceptVisitorしなきゃならないようで面倒ぃので、とりあえず拡張メソッドにしました。デフォルトの整形オプションが気に食わないのですが、フォーマット設定が超細かくて100個以上のboolをON/OFFしなきゃいけないのでとりあえず放置。

構文木の取得自体は超単純で、new CSharpParser()してParse。そしてツリーを辿るのはLINQ to Xmlと同じ感覚でDescendantsやAncestors、Childrenなどなどが用意されているので、一発で分かりますね!そしてOfTypeで自分の欲しいのにフィルタリング、と。この辺は超簡単。そして名前を変えたければ、プロパティに代入するだけ。非常に楽ちんです、素晴らしい……!

vs Roslyn

さて、じゃあRoslynでも同じことをやってみましょう。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Roslyn.Compilers.CSharp;
 
class Program
{
    static void Main(string[] args)
    {
        var code = File.ReadAllText(@"../../Program.cs");
        var tree = SyntaxTree.ParseCompilationUnit(code);
 
        var program = tree.Root.DescendentNodes().OfType<ClassDeclarationSyntax>().First();
 
        var newNode = new ClassNameRewriter(new Dictionary<ClassDeclarationSyntax, string> { { program, "Hogegram" } })
            .Visit(tree.Root);
 
        Console.WriteLine(newNode.ToString());
    }
}
 
public class ClassNameRewriter : SyntaxRewriter
{
    readonly IDictionary<ClassDeclarationSyntax, string> replaceNames;
 
    public ClassNameRewriter(IDictionary<ClassDeclarationSyntax, string> replaceNames)
    {
        this.replaceNames = replaceNames;
    }
 
    protected override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
    {
        var oldIdentifierToken = node.Identifier;
 
        if (replaceNames.ContainsKey(node))
        {
            var newNode = node.Update(node.Attributes,
               node.Modifiers, node.Keyword,
               Syntax.Identifier(
                   oldIdentifierToken.LeadingTrivia,
                   replaceNames[node], // ここだけ!
                   oldIdentifierToken.TrailingTrivia),
               node.TypeParameterListOpt, node.BaseListOpt,
               node.ConstraintClauses, node.OpenBraceToken,
               node.Members, node.CloseBraceToken,
               node.SemicolonTokenOpt);
 
            return newNode;
        }
        else
        {
            return base.VisitClassDeclaration(node);
        }
    }
}

構文木の取得はこちらも簡単です、SyntaxTree.ParseCompilationUnitだけ。ツリーの辿り方も似ていて、DescendentNodes、ChildNodes、Ancestors、と、戸惑うことなく使える感じです。OfTypeでフィルタして絞り込みも同じ。書き換えたコードの出力は、こちらはToStringだけでOK、しかもフォーマットルールは何の設定もいらずVS標準と同じ状態になっているので楽ちん。

が、しかし、こちらはクラス名の書き換えが面倒。Expression Treeと同じく基本的にイミュータブルになっているので、書き換えはプロパティに代入するだけ、とはいかず、大掛かりな仕掛けが必要です。というかこの程度のためだけにVisitorとか……。一応ReplaceNodeとかUpdateとかもあるんですが、うーん、まあ、何というか、よくわかってないので深く突っ込まれると窮します:)

まとめ

提供する機能は同じでも、使い心地とかは、両者、結構違くなるのではという印象。どちらも発展途上なのですが、ライセンス的にRoslynは今は使えないのに比べると、NRefactoryはMITライセンスで、アルファ版なのを留意しておけば使えるのではというのが、私的には大きいかなあ。割と切迫してC# Parserが必要なところなので、ここはちょっとNRefactoryを使い込んでみたいなあ、なんて思っています。いや、そこまでDeepに使い倒すというより簡単なコード解析と置換程度なので、しっかり使ってバグ報告、とかの貢献は出来なさそうですんがー。

ImplicitQueryString - 暗黙的変換を活用したC#用のクエリストリング変換ライブラリ

QueryStringかったるいですね、変換するのが。intに。boolに。それ以外に。そのままじゃどうしようもなくストレスフル。そこで、以下のように書けるライブラリを作りました。勿論NuGetでのインストールも可能です。あと、ライブラリといっても例によって.csファイル一個だけなので、導入は超お手軽。

以下はASP.NETの例ですが、NameValueCollectionを使っているものは全て対象になります。QueryStringだけじゃなくてRequest.Formなどにも使えますね。

// using Codeplex.Web;
 
int count;
string query;
bool? isOrdered; // support nullable
 
protected void Page_Load(object sender, EventArgs e)
{
    count = Request.QueryString.ParseValue("c");
    query = Request.QueryString.ParseValue("q");
    isOrdered = Request.QueryString.ParseValue("ord");
}

NameValueCollectionへの拡張メソッドとして実装しているので、using Codeplex.Webを忘れないように。ポイントは「型の明示が不要」というところです。クエリストリングを解析したい時って、通常はフィールドで既に変数自体は作っているのではないかと思います。なら、代入時に左から型を推論させてしまえばいいわけです、モダンなC#はわざわざ明示的に型なんて書かない、書きたくない。なのでこのライブラリ、ImplicitQueryStringではParseValueだけで全ての基本型(int, long, bool, string, DateTimeなど)へと型指定不要で代入可能となっています。代入先の型がNullableである場合は、キーが見つからなかったりパースに失敗した場合はnullにすることもサポートしてます。

また、よくあるクエリストリングはUrlEncodeされているのでデコードしたかったりするケースにも簡単に対応しています、UrlDecodeを渡すだけ!他にもEnumへの変換も出来ますし、キーが見つからなかったり変換不可能だったりした場合は指定したデフォルト値を返すParseValueOrDefault/ParseEnumOrDefaultも用意してあります。キーがあるかチェックするContainsKeyも。

enum Sex
{
    Unknown = 0, Male = 1, Female = 2
}
 
enum BloodType
{
    Unknown, A, B, AB, O
}
 
int age;
string name;
DateTime? requestTime;  // nullableやDateTimeもサポート
bool hasChild;
Sex sex;               // enumもいけます
BloodType bloodType;
 
protected void Page_Load(object sender, EventArgs e)
{
     // こんなQueryStringがあるとして
    // a=20&n=John%3dJohn+Ab&s=1&bt=AB
 
    // ageは左から推論してintを返します
    age = Request.QueryString.ParseValue("a"); // 20
 
    // UrlDecodeしたstringが欲しい時は第二引数にメソッドそのものを渡すだけ
    name = Request.QueryString.ParseValue("n", HttpUtility.UrlDecode); // John=John Ab
 
    // 代入先の型がnullableの場合は、もしキーが見つからなかったりパースに失敗したらnullにしてくれます
    requestTime = Request.QueryString.ParseValue("t", HttpUtility.UrlDecode); // null
 
    // キーが見つからなかったりパースに失敗したら指定した値を返してくれます
    hasChild = Request.QueryString.ParseValueOrDefault("cld", false); // false
 
    // Enumの変換は数字の場合でも文字列の場合でも、どちらでも変換可能です
    sex = Request.QueryString.ParseEnum<Sex>("s"); // Sex.Male
    bloodType = Request.QueryString.ParseEnumOrDefault<BloodType>("bt", BloodType.Unknown); // BloodType.AB
 
    // ContainsKeyはキーの有無をチェックします
    var hasFlag = qs.ContainsKey("flg"); // false
}

これで、あらゆるケースでサクッと変換することが可能なのではかと思います。ちなみに、ParseValue/ParseEnumでキーが見つからなかった場合はKeyNotFoundExceptionを返します。ParseValueで失敗したらFormatException、ParseEnumで失敗したらArgumentExceptionです。この二つが分かれているのは、int.Parseとかに渡しているだけなので、そちらの都合です。

仕組み

ImplicitQueryStringという名前がネタバレなのですが、単純に暗黙的変換をしているだけです。左から推論とか、ただのハッタリです。neue cc - dynamicとQueryString、或いは無限に不確定なオプション引数についてを書いたときに、わざわざdynamicを持ち出さなくても、暗黙的変換で十分じゃないの?そのほうが利便性も上じゃないの?と思ったのでやってみたら、やはりドンピシャでした。

暗黙的変換は、あまり使う機能じゃあないと思いますし、実際、乱用すべきでない機能であることには違いないのですが、たまに活用する分には中々刺激的で創造性に満ちています。大昔からあるけれど普段使わない機能だなんて、ロストテクノロジーっぽくて浪漫に溢れています。

さて、ただし活用するにはコード中に型を並べなければならないので、汎用的というわけではないのもそうなのですが、人力で書くのもシンドイところ。勿論人力でやる気はしないのでT4 Templateを使いました。

<#@ template language="C#" #>
<#@ output extension="cs" #>
<#@ assembly Name="System.Core.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="System.Reflection" #>
<#
    var methods = typeof(Convert).GetMethods(BindingFlags.Static | BindingFlags.Public);
 
    var converters = methods.Where(x => x.Name.StartsWith("To"))
        .Select(x => Regex.Replace(x.Name, "^To", ""))
        .Where(x => !x.StartsWith("Base64") && x != "String")
        .Distinct()
        .ToArray();
#>
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
 
namespace Codeplex.Web
{
    public static class NameValueCollectionExtensions
    {
        public static ConvertableString ParseValue(this NameValueCollection source, string key)
        {
            return ParseValue(source, key, null);
        }
 
        public static ConvertableString ParseValue(this NameValueCollection source, string key, Func<string, string> converter)
        {
            var values = source.GetValues(key);
            if (values == null) return new ConvertableString(null);
 
            var value = values[0];
            return new ConvertableString(converter == null ? value : converter(value));
        }
 
        // 中略
    }
 
    public struct ConvertableString
    {
        public readonly string Value;
 
        public ConvertableString(string value)
        {
            this.Value = value;
        }
 
<# foreach (var converter in converters) { #>
        public static implicit operator <#= converter #>(ConvertableString self)
        {
            if (self.Value == null) throw new KeyNotFoundException();
            return <#= converter #>.Parse(self.Value);
        }
 
        public static implicit operator <#= converter #>?(ConvertableString self)
        {
            <#= converter #> value;
            return (self.Value != null && <#= converter #>.TryParse(self.Value, out value))
                ? new Nullable<<#= converter #>>(value)
                : null;
        }
 
<# } #>
        public static implicit operator String(ConvertableString self)
        {
            return self.Value;
        }
 
        public override string ToString()
        {
            return Value;
        }
    }
}

以上のコードから、T4生成部分を取り出すと

public static implicit operator Boolean(ConvertableString self)
{
    if (self.Value == null) throw new KeyNotFoundException();
    return Boolean.Parse(self.Value);
}
 
public static implicit operator Boolean?(ConvertableString self)
{
    Boolean value;
    return (self.Value != null && Boolean.TryParse(self.Value, out value))
        ? new Nullable<Boolean>(value)
        : null;
}
 
public static implicit operator Char(ConvertableString self)
{
    if (self.Value == null) throw new KeyNotFoundException();
    return Char.Parse(self.Value);
}
 
public static implicit operator Char?(ConvertableString self)
{
    Char value;
    return (self.Value != null && Char.TryParse(self.Value, out value))
        ? new Nullable<Char>(value)
        : null;
}
 
// 以下SByte, Byte, Int16, Int32, Int64, ..., DateTimeと繰り返し

といったコードがジェネレートされます。清々しいまでのゴリ押しっぷり。一周回ってむしろエレガント。ConvertableStringのほうでKeyNotFoundExceptionを出すのがイケてないのですが、まあそこはShoganaiかなー、ということで。さて、それはそれとして、やっぱVisual Studioに組み込みで、こういった自動生成のテンプレートエンジンが用意されているというのは非常に嬉しいところです。

まとめ

特にASP.NETを使っている方々にどうぞ。ASP.NET MVCの人はしらにゃい。なのでいつもの私だったら.NET 4.0専用にするところが、今回は.NET 3.5でも大丈夫!Visual Studio 2008でも動作確認取りました!

それにしても実際ASP.NETを使いこなしてる人はクエリストリングの取得はどんな風にやっていたものなのでしょうかー。何かしらの手法がないとシンドすぎやしませんか?そう思って検索してみたんですが、どうにも見つからず。さすがに ParseValueAsInt() とかって拡張メソッドぐらい作ってやり繰りは私も大昔していたし、それでintに対応しておけばほぼほぼOKではありますが(拡張メソッドがなかった時代はさすがに想像したくない!)。そう考えると、ちょっとこれはやりすぎ感も若干。でも、一度できあがればずっと使えますしね。というわけでImplicitQueryString、是非是非使ってみてください。本当に楽になれると思います。

dynamicとQueryString、或いは無限に不確定なオプション引数について

どうもこんばんわ、30時間も寝てないわー。まあ、お陰で真昼間の就業中にうつらうつらしてましたが!ちなみにGRAVITY DAZEにはまって延々とプレイしてたから寝てないだけです。PS陣営にくだるなんて!さて、それはそれとしてBlog更新頻度の低下っぷりはお察し下さい。そんなこんなで最近の仕事(仕事ですって!仕事してるんですって!?)はASP.NETなんですが、色々アレですね。ふふふ。ソウルジェムは真っ黒と真っ白を行ったり来たりしてて楽しいです。まあ、ようするに楽しいです。楽しいのはいいとしてBlogが停滞するのはいくないので、電車でGRAVITY DAZEやりながらQueryStringうぜえええええ、とか悶々としたので帰り道に殺害する算段を整えていました。

QueryStringって、qとかnumとかiとかsとか、それそのものだけじゃ何を指してるかイミフなので、C#的にはちゃんと名前をつけてやりたいよね。だから、ただシンプルに触れるというだけじゃダメで。あと、コンバートもしたいよね。数字はintにしたいしboolは当然boolなわけで。さて、そんな時に私たちが持つツールと言ったら、dynamicです。C#とスキーマレスな世界を繋ぐ素敵な道具。今日も活躍してもらうことにしましょう。たまにしか出番ないですからね。

private int id;
private int? count;
private string keyword;
private bool isOrdered;
 
protected void Page_Load(object sender, EventArgs e)
{
    // id=10&c=100&q=hogehoge&od=true というクエリストリングが来る場合
 
    // dynamicに変換!
    var query = this.Request.QueryString.AsDynamic();
 
    id = query.id; // キャストは不要、書くの楽ですね!(キーが存在しないと例外)
    count = query.c; // 型がnullableならば、存在しなければnull
    keyword = query.q; // stringの場合も存在しない場合はnull
    isOrdered = query.od(false); // メソッドのように値を渡すと、キーが存在しない場合のデフォルト値になる
}

ASP.NETの例ですが、どうでしょう、まぁまぁ良い感じじゃあなくて?ちなみに最初はnull合体演算子(??)を使いたかったのですが、DynamicObjectでラップした時点で、そもそも存在がnullじゃないということで、何をどうやってもうまく活用できなくて泣いた。しょうがないのでメソッド形式でデフォルト値を渡すことでそれっぽいような雰囲気に誤魔化すことにしました、とほほほ。

dynamicとオプション引数

クエリストリングを生成することも出来ます。

// 戻り値はdynamicで空のDynamicQueryStringを生成
var query = DynamicQueryString.Create();
 
// オプション引数の形式で書くと……?
query(id: 100, c: 100, q: "hogehoge", od: true);
 
Console.WriteLine(query.ToString()); // id=10&c=100&q=hogehoge&od=true

といった感じで、面白いのがオプション引数の使い方。匿名型渡しのように、スマートにKey:Valueを渡すことを実現しています。dynamicなので引数名は完全自由。個数も完全自由。非常にC#らしくなくC#らしいところが最高にCOOL。匿名型とどちらがいいの?というと何とも言えないところですが、面白いには違いないし、面白いは正義。C#も工夫次第でまだまだ色々なやり方が模索できるんですよー、ってところです。

実装など

眠いのでコードの解説はしません(ぉ。DynamicObjectの実装方法はneue cc - C# DynamicObjectの基本と細かい部分についてでどーぞ。overrideしていくだけなので、別に難しくもなんともないので是非是非やってみてください。ちなみに無限のオプション引数を実現している箇所は binder.CallInfo.ArgumentNames.Zip(args, (key, value) => new { key, value }) です。

public static class NameValueCollectionExtensions
{
    public static dynamic AsDynamic(this NameValueCollection queryString)
    {
        return new DynamicQueryString(queryString);
    }
}
 
public class DynamicQueryString : DynamicObject
{
    NameValueCollection source;
 
    public DynamicQueryString()
    {
        this.source = new NameValueCollection();
    }
 
    public DynamicQueryString(NameValueCollection queryString)
    {
        this.source = queryString;
    }
 
    public static dynamic Create()
    {
        return new DynamicQueryString();
    }
 
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        var value = source[binder.Name];
        result = new StringMember((value == null) ? value : value.Split(',').FirstOrDefault());
        return true;
    }
 
    public override bool TryInvoke(InvokeBinder binder, object[] args, out object result)
    {
        foreach (var item in binder.CallInfo.ArgumentNames.Zip(args, (key, value) => new { key, value }))
        {
            source.Add(item.key, item.value.ToString());
        }
 
        result = this.ToString();
        return true;
    }
 
    public override bool TryConvert(ConvertBinder binder, out object result)
    {
        if (binder.Type != typeof(string))
        {
            result = null;
            return false;
        }
        else
        {
            result = this.ToString();
            return true;
        }
    }
 
    public override string ToString()
    {
        return string.Join("&", source.Cast<string>().Select(key => key + "=" + source[key]));
    }
 
    public override IEnumerable<string> GetDynamicMemberNames()
    {
        return source.Cast<string>();
    }
 
    class StringMember : DynamicObject
    {
        readonly string value;
 
        public StringMember(string value)
        {
            this.value = value;
        }
 
        public override bool TryInvoke(InvokeBinder binder, object[] args, out object result)
        {
            var defaultValue = args.First();
 
            try
            {
                result = (value == null)
                    ? defaultValue
                    : Convert.ChangeType(value, defaultValue.GetType());
            }
            catch (FormatException) // 真面目にやるならType.GetTypeCodeでTypeを分けて、例外キャッチじゃなくてTryParseのほうがいいかな?
            {
                result = defaultValue;
            }
 
            return true;
        }
 
        public override bool TryConvert(ConvertBinder binder, out object result)
        {
            try
            {
                var type = (binder.Type.IsGenericType && binder.Type.GetGenericTypeDefinition() == typeof(Nullable<>))
                    ? binder.Type.GetGenericArguments().First()
                    : binder.Type;
 
                result = (value == null)
                    ? null
                    : Convert.ChangeType(value, binder.Type);
            }
            catch (FormatException)
            {
                result = null;
            }
 
            return true;
        }
 
        public override string ToString()
        {
            return value ?? "";
        }
    }
}

コンバートに対応させるために、要素一つだけのDynamicObjectを中で用意しちゃってます。そこがポイント、といえばポイント、かしら。GetDynamicMemberNamesはデバッガの「動的ビュー」に表示されるので、Visual Studioに優しいコードを書くなら必須ですね。

まとめ

dynamicはC#と外の世界を繋ぐためのもの。今日もまた一つ繋いでしまった。それはともかくとして、一番最初、DynamicJsonを実装した頃にも言ったのですが、dynamicはDSL的な側面もあって、普通に楽しめますので、ちょっと頭をひねって活用してみると、また一つ、素敵な世界が待っています。

今回はコード書くのに2時間、この記事を書くのに1時間、でしたー。

C#のO/Rマッパーのパフォーマンス測定 Part2

以前にneue cc - C#のMicro-ORM(Dapper, Massive, PetaPoco)についてで計測したのですが、@shibayan先生がEF 4.1のDbContextのRaw SQL Queriesはどうなの?とTwitterで言ってたのを見かけたので、再度測ってみました。ていうか私はDbContextとObjectContextの違いすら分かってないんですが、DbContextは軽量な感じっぽいそうです、はい。

ベンチマークは前回と引き続きdapper-dot-netのリポジトリにあるベンチを使用しました。それにEFのバージョンを4.2に上げて、DbContextのSqlQueryを追加。また、id:taediumさんの作られているSomaは最新バージョンの1.6にしておきました。そして私の作成しているMicro-ORMであるDbExecutorのベンチも引き続き載せています。Visual Studio 2005時代のデータアクセステクノロジである型付きDataSetも加えてあります。

Mapper Query (non-buffered) took 55ms
Dynamic Mapper Query (buffered) took 56ms
Dynamic Mapper Query (non-buffered) took 56ms
hand coded took 57ms
DbExecutor ExecuteReader(Hand Coded) took 59ms
Dapper.Cotrib took 60ms
OrmLite QueryById took 60ms
DbExecutor Select took 60ms
Mapper Query (buffered) took 61ms
PetaPoco (Fast) took 62ms
PetaPoco (Normal) took 63ms
DbExecutor SelectDynamic took 63ms
Dynamic Massive ORM Query took 64ms
DbExecutor ExecuteReaderDynamic(Hand Coded) took 64ms
BLToolkit took 82ms
Simple.Data took 87ms
Linq 2 SQL Compiled took 96ms
DataSet took 108ms
SubSonic Coding Horror took 116ms
Entity framework CompiledQuery took 120ms
NHibernate SQL took 125ms
NHibernate Session.Get took 128ms
NHibernate HQL took 135ms
Soma Find took 164ms
NHibernate Criteria took 170ms
Linq 2 SQL ExecuteQuery took 207ms
Linq 2 SQL took 597ms
NHibernate LINQ took 610ms
Entity framework ExecuteStoreQuery took 634ms
Entity framework DbContext SqlQuery took 670ms
Entity framework ESQL took 725ms
Entity framework took 900ms
Entity framework No Tracking took 903ms
SubSonic ActiveRecord.SingleOrDefault took 3736ms

hand codedがExecuteReaderを手で回した手書き、「Mapper Query」はDapperのことです。複数種類があるのはオプション違い。DbExecutor(太字にしています)も同様に4種類で測っています。上位陣は何回も測ると適当に入れ替わりますし、速度的にも500回ブン回して数msとか、ほとんど誤差範囲でいいのではかと思います。

というわけで、ええと、EntityFrameworkの遅さが目立ちますね、CompiledQueryは割といいのですが、むしろそうしないと絶望的。特に、文字列で生SQLを書くはずのExecuteStoreQueryやSqlQueryがクソみたいに遅いのはどういうことなのかと問いつめたい。更に、軽量なはずのDbContextのSqlQueryよりもObjectContextのExecuteStoreQueryのほうが速いとか、頭痛くなります。オマケ機能だと思ってテキトーなのではかと思われる気がかなりします、MSもっと本気出せ。

DataSetが割と健闘しちゃってるのが、DataSet嫌いな私としては何とも言い難い感じです(笑)

まぁ、DbExecutorが速さと使い勝手を両立しているので、Micro-ORMでいいならDbExecutor使うといいですよ、はい。メンテしてないって?はい、そうですね……。割と真面目な話、色々機能拡張したいというかしなければならない必然性とかが迫っていたりしたりしなかったりするので、近いうちに再度動き出すつもりではいます。なので使ってみるといいと思います。

RxとパフォーマンスとユニットテストとMoles再び

C# Advent Calendar 2011、順調に進んでいますね。どのエントリも力作で大変素晴らしいです。私はこないだModern C# Programming Style Guideというものを書きました。はてブ数は現段階で45、うーん、あまり振るわない……。私の力不足はともかくとしても、他の言語だったらもっと伸びてるだろうに、と思うと、日本のC#の現状はそんなものかなあ、はぁ、という感じではあります。はてブが全てではない(むしろ斜陽?)とはいえ、Twitterでの言及数などを見ても、やっぱまだまだまだまだまだまだ厳しいかなあ、といったところ。Unityなどもあって、見ている限りだと人口自体は着実に増えている感じではありますけれど、もっともっと、関心持ってくれる人が増えるといいな。私も微力ながら尽力したいところです。

ところで、id:ZOETROPEさんのAdvent Calendarの記事、Reactive Extensionsでセンサプログラミングが大変素晴らしい!センサー、というと私だとWindows Phone 7から引っ張ってくるぐらいしか浮かばないのですが(最近だとKinectもHotですか、私は全然触れてませんが……)おお、USB接続のレンジセンサ!完全に門外漢な私としては、そういうのもあるのか!といったぐらいなわけですが、こうしてコード見させていただくと、実践的に使うRxといった感じでとてもいいです。

記事中で扱われているトピックも幅広いわけですが、まず、パフォーマンスに関しては少し補足を。@okazukiさんの見せてもらおうじゃないかReactive Extensionsの性能とやらを! その2のコメント欄でもちょっと言及したのですが、この測り方の場合、Observable.Rangeに引っ張られているので、ベンチマークの値はちょっと不正確かな、と思います。

// 1000回イベントが発火(発火の度に長さ3000のbyte配列が得られる)を模写
static IObservable<byte[]> DummyEventsRaised()
{
    return Observable.Repeat(new byte[3000], 1000, Scheduler.Immediate);
}
 
// 配列をバラす処理にObservable.Rangeを用いた場合
static IObservable<byte> TestObservableRange()
{
    return Observable.Create<byte>(observer =>
    {
        return DummyEventsRaised()
            .Subscribe(xs =>
            {
                Observable.Range(0, xs.Length, Scheduler.Immediate).ForEach(x => observer.OnNext(xs[x]));
            });
    });
}
 
// 配列をバラす処理にEnumerable.Rangeを用いた場合(ForEachはIxのもの)
static IObservable<byte> TestEnumerableRange()
{
    return Observable.Create<byte>(observer =>
    {
        return DummyEventsRaised()
            .Subscribe(xs =>
            {
                Enumerable.Range(0, xs.Length).ForEach(x => observer.OnNext(xs[x]));
            });
    });
}
 
// SelectManyでバラす場合
static IObservable<byte> TestSelectMany()
{
    return DummyEventsRaised().SelectMany(xs => xs);
}
 
static void Main(string[] args)
{
    // ベンチマーク補助関数
    Action<Action, string> bench = (action, label) =>
    {
        var sw = Stopwatch.StartNew();
        action();
        Console.WriteLine("{0,-12}{1}", label, sw.Elapsed);
    };
 
    // 配列をばらすケースは再度連結する(ToList)
    bench(() => TestObservableRange().ToList().Subscribe(), "Ob.Range");
    bench(() => TestEnumerableRange().ToList().Subscribe(), "En.Range");
    bench(() => TestSelectMany().ToList().Subscribe(), "SelectMany");
    // 配列をばらして連結せず直接処理する場合
    bench(() => TestSelectMany().Subscribe(), "DirectRx");
    // byte[]をばらさず直接処理する場合
    bench(() => DummyEventsRaised().Subscribe(xs => { foreach (var x in xs);}), "DirectLoop");
 
    // 実行結果
    // Ob.Range    00:00:02.2619670
    // En.Range    00:00:00.2600460
    // SelectMany  00:00:00.2701137
    // DirectRx    00:00:00.0852836
    // DirectLoop  00:00:00.0152816
}

得られる配列をダイレクトに処理するとして、Observable.Rangeで配列のループを回すと論外なほど遅い。のですが、しかし、この場合ですとEnumerable.Rangeで十分なわけで、そうすれば速度は全然変わってきます(もっと言えば、ここではEnumerable.Rangeではなくforeachを使えば更に若干速くなります)。更に、これは配列を平坦化している処理とみなすことができるので、observerを直に触らず、SelectManyを使うこともできますね。そうすれば速度はほとんど変わらず、コードはよりすっきり仕上がります。

と、いうわけで、遅さの原因はObservable.Rangeです。Rangeが遅いということはRepeatやGenerateなども同様に遅いです。遅い理由は、値の一つ一つをISchedulerを通して流しているから。スケジューラ経由であることは大きな柔軟性をもたらしていますが、直にforeachするよりもずっとずっと遅くなる。なので、Enumerableで処理出来る局面ならば、Enumerableを使わなければなりません。これは、使うほうがいい、とかではなくて、圧倒的な速度差となるので、絶対に、Enumerableのほうを使いましょう。

また、一旦配列をバラして、再度連結というのは、無駄極まりなく、大きな速度差にも現れてきます。もし再度連結しないでそのまま利用(ベンチ結果:DirectRx)すれば直接ループを回す(ベンチ結果:DirectLoop)よりも5倍程度の遅さで済んでいます。このぐらいなら許容範囲と言えないでしょうか?とはいえ、それでも、遅さには違いないわけで、避けれるのならば避けたほうがよいでしょう。

ZOETROPEさんの記事にあるように、ここはばらさないほうが良い、というのが結論かなあ、と思います。正しくは上流ではばらさない。一旦バラしたものは復元不可能です。LINQで、パイプラインで処理を接続することが可能という性質を活かすのならば、なるべく後続で自由の効く形で流してあげたほうがいい。アプリケーション側でバラす必要があるなら、それこそSelectMany一発でばらせるのだから。

例えばWebRequestで配列状態のXMLを取ってくるとします。要素は20個あるとしましょう。最初の文字列状態だけを送られてもあまり意味はないので、XElement.Parseして、実際のクラスへのマッピングまではやります。例えばここではPersonにマッピングするとして、長さ1のIObservable<Person[]>です。しかし、それをSelectManyして長さ20のIObservable<Person>にはしないほうがいい。ここでバラしてしまうと長さという情報は消滅してしまうし、一回のリクエスト単位ではなくなるのも不都合が生じやすい。もしアプリケーション的にフラットになっていたほうが都合が良いのなら、それはまたそれで別のメソッドとして切り分けましょう。

成功と失敗の一本化

ZOETROPEさんの記事の素晴らしいのは、通常のルート(DataReceived)と失敗のルート(ErrorReceived)を混ぜあわせているところ!これもまたイベントの合成の一つの形なわけなんですねー。こういう事例はWebClientのDownloadStringAsyncのような、EAP(Eventbased Asynchronous Programming)をTaskCompletionSourceでラップしてTaskに変換する 方法: タスクに EAP パターンをラップする←なんかゴチャゴチャしていますが、TrySetCanceled, TrySetException, TrySetResultで結果を包んでいます、というのと似た話だと見なせます。

WebClientではEventArgsがCancelledやErrorといったステータスを持っているのでずっと単純ですが、SerialPortではエラーは別のイベントでやってくるのですね。というわけで、私もラップしてみました。

public static class SerialPortExtensions
{
    // 面倒くさいけれど単純なFromEventでのイベントのRx化
    public static IObservable<SerialDataReceivedEventArgs> DataReceivedAsObservable(this SerialPort serialPort)
    {
        return Observable.FromEvent<SerialDataReceivedEventHandler, SerialDataReceivedEventArgs>(
            h => (sender, e) => h(e), h => serialPort.DataReceived += h, h => serialPort.DataReceived -= h);
    }
 
    public static IObservable<SerialErrorReceivedEventArgs> ErrorReceivedAsObservable(this SerialPort serialPort)
    {
        return Observable.FromEvent<SerialErrorReceivedEventHandler, SerialErrorReceivedEventArgs>(
            h => (sender, e) => h(e), h => serialPort.ErrorReceived += h, h => serialPort.ErrorReceived -= h);
    }
 
    // DataReceived(プラスbyte[]化)とErrorReceivedを合成する
    public static IObservable<byte[]> ObserveReceiveBytes(this SerialPort serialPort)
    {
        var received = serialPort.DataReceivedAsObservable()
            .TakeWhile(e => e.EventType != SerialData.Eof) // これでOnCompletedを出す
            .Select(e =>
            {
                var buf = new byte[serialPort.BytesToRead];
                serialPort.Read(buf, 0, buf.Length);
                return buf;
            });
 
        var error = serialPort.ErrorReceivedAsObservable()
            .Take(1) // 届いたらすぐに例外だすので長さ1として扱う(どうせthrowするなら関係ないけど一応)
            .Do(x => { throw new Exception(x.EventType.ToString()); });
 
        return received.TakeUntil(error); // receivedが完了した時に同時にerrorをデタッチする必要があるのでMergeではダメ
    }
}

成功例と失敗例を合成して一本のストリーム化。また、DataReceivedはそのままじゃデータすっからかんなので、Selectでbyte[]に変換してあげています。これで、ObserveReceiveBytes拡張メソッドを呼び出すだけで、かなり扱いやすい形になっている、と言えるでしょう。パフォーマンスも、これなら全く問題ありません。

MolesとRx

と、ドヤ顔しながら書いていたのですが、とーぜんセンサーの実物なんて持ってませんので動作確認しようにもできないし。ま、まあ、そういう時はモックとか用意して、ってSerialDataReceivedEventArgsはパブリックなコンストラクタないし、ああもうどうすればー。と、そこで出てくるのがMoles - Isolation framework。以前にRx + MolesによるC#での次世代非同期モックテスト考察という記事で紹介したのですが、めちゃくちゃ強力なモックライブラリです。パブリックなコンストラクタがないとか関係なくダミーのインスタンスを生成可能だし、センサーのイベントだから作り出せないし、なんてこともなく自由にダミーのイベントを発行しまくれます。

[TestClass]
public class SerialPortExtensionsTest : ReactiveTest
{
    [TestMethod, HostType("Moles")]
    public void ObserveReceiveBytesOnCompleted()
    {
        // EventArgsを捏造!
        var chars = new MSerialDataReceivedEventArgs() { EventTypeGet = () => SerialData.Chars };
        var eof = new MSerialDataReceivedEventArgs() { EventTypeGet = () => SerialData.Eof };
        // SerialPort::BytesToRead/SerialPort::Readで何もしない
        MSerialPort.AllInstances.BytesToReadGet = (self) => 0;
        MSerialPort.AllInstances.ReadByteArrayInt32Int32 = (self, buffer, offset, count) => 0;
 
        var scheduler = new TestScheduler();
 
        // 時間10, 20, 30, 40でSerialData.Charsのイベントを、時間50でEofのイベントを発行
        MSerialPortExtensions.DataReceivedAsObservableSerialPort = _ => scheduler.CreateHotObservable(
                OnNext(10, chars),
                OnNext(20, chars),
                OnNext(30, chars),
                OnNext(40, chars),
                OnNext(50, eof))
            .Select(x => (SerialDataReceivedEventArgs)x);
 
        // 走らせる(戻り値のbyte[]はどうでもいいので無視するためUnitに変換)
        var result = scheduler.Start(() => new SerialPort().ObserveReceiveBytes().Select(_ => Unit.Default), 0, 0, 100);
 
        result.Messages.Is(
            OnNext(10, Unit.Default),
            OnNext(20, Unit.Default),
            OnNext(30, Unit.Default),
            OnNext(40, Unit.Default),
            OnCompleted<Unit>(50));
    }
 
    [TestMethod, HostType("Moles")]
    public void ObserveReceiveBytesOnError()
    {
        // EventArgsを捏造!
        var chars = new MSerialDataReceivedEventArgs() { EventTypeGet = () => SerialData.Chars };
        var eof = new MSerialDataReceivedEventArgs() { EventTypeGet = () => SerialData.Eof };
        // SerialPort::BytesToRead/SerialPort::Readで何もしない
        MSerialPort.AllInstances.BytesToReadGet = (self) => 0;
        MSerialPort.AllInstances.ReadByteArrayInt32Int32 = (self, buffer, offset, count) => 0;
 
        var scheduler = new TestScheduler();
 
        // 時間10, 20, 30, 40でSerialData.Charsのイベントを、時間50でEofのイベントを発行
        MSerialPortExtensions.DataReceivedAsObservableSerialPort = _ => scheduler.CreateHotObservable(
                OnNext(10, chars),
                OnNext(20, chars),
                OnNext(30, chars),
                OnNext(40, chars),
                OnNext(50, eof))
            .Select(x => (SerialDataReceivedEventArgs)x);
 
        /* ↑までOnCompletedのものと共通 */
 
        // 時間35でErrorのイベントを発行
        MSerialPortExtensions.ErrorReceivedAsObservableSerialPort = _ => scheduler.CreateHotObservable(
            OnNext<SerialErrorReceivedEventArgs>(35, new MSerialErrorReceivedEventArgs()));
 
        // 走らせる(戻り値のbyte[]はどうでもいいので無視するためUnitに変換)
        var result = scheduler.Start(() => new SerialPort().ObserveReceiveBytes().Select(_ => Unit.Default), 0, 0, 100);
 
        // Exceptionの等値比較ができないので、バラしてAssertする
        result.Messages.Count.Is(4);
 
        result.Messages[0].Is(OnNext(10, Unit.Default));
        result.Messages[1].Is(OnNext(20, Unit.Default));
        result.Messages[2].Is(OnNext(30, Unit.Default));
 
        result.Messages[3].Value.Kind.Is(NotificationKind.OnError);
        result.Messages[3].Time.Is(35);
    }
}

アサーションに使っているIsメソッドは、いつも通りChaining Assertionです。

Molesがいくら強力だとは言っても、イベントをそのまま乗っ取るのはデリゲートの差し替えなどで、割と面倒だったりします。しかし、FromEventでラップしただけのIObservable<T>を用意しておくと…… それを差し替えるだけで済むので超簡単になります。イベント発行については、TestScheduler(Rx-Testingを参照しておく)で、仮想時間で発行する値を作ってしまうと楽です。こういう、任意の時間で任意の値、というダミーの用意もFromEventでラップしただけのIObservable<T>があると、非常に簡単になります。

あとは、scheduler.Startで走らせると(3つの引数はそれぞれcreated, subscribed, disposedの仮想時間、何も指定しないと…… 実は0始まり「ではない」ことに注意。100,200,1000がデフォなので、0はすっ飛ばされています)、その戻り値で結果を受け取って、Messagesに記録されているので、それにたいしてアサートメソッドをしかける。

実に簡単ですね!Molesの力とRxの力が組み合わさると、イベントのテストが恐ろしく簡単になります。素敵じゃないでしょうか?

まとめ

テストなしで書いてたコードは、Molesでテスト走らせたら間違ってました。TakeWhileの条件が==だったのと、Mergeで結合していたり……。はっはっは、ちゃんとユニットテストは書かないとダメですね!そして、Molesのお陰でちゃんと動作するコードが書けたので恥を欠かなくてすみました、やったね。

Modern C# Programming Style Guide

C# Advent Calendar 2011、ということで、C# 4.0時代のプログラミングスタイルについて説明してみます。モダン、というけれど、某書のように変態的なことじゃなくて、むしろ基本的な話のほうです。こういったものはナマモノなので、5.0になればまた変わる、6.0になればまた変わる。変わります。古い話を間に受けすぎないこと(歴史を知るのは大事だけど、そのまま信じるのは別の話)、常に知識をリフレッシュするようにすること。そういうのが大事よね。でも、だからってモダンに書けなきゃダメ!なんてことはありません。ただ、知ること、少しずつ変えていくこと、そういうのは大事よね、って。

ところでしかし、私の主観がかなり入っているので、その辺は差っ引いてください。

1. varを使う

C# 3.0から搭載された型推論での宣言。出た当初には散々議論があって、今もたまに否の意見が出てきたりもしますが、varは使いましょう。積極的に。何にでも。国内的にも世界的にもMicrosoft的にも、var積極利用の方向で傾いているように見えます。また、最近流行りの関数型言語(Haskell, Scala, F#)は、少なくともC#のvarで可能な範囲は全て推論を使いますね←C#のvarはそれらに比べれば遥かに貧弱ですからね。そういったこともあるので、使わない理由もないでしょう。

var person = new Person();
var dict = new Dictionary<string, Tuple<int, Person>>();
var query = Enumerable.Range(1, 10).Select(x => x * 10);

varの利点は、何といっても書いていて楽なことです。はい、圧倒的に楽です。そして、型宣言の長さが一致するので「実は見やすい」というのもポイント高し。たった3文字の短さと相まって、ソースコードが綺麗になります。また、必ず変数の初期化を伴う、というのも良いことです。

欠点は「メソッドの戻り値などは宣言を見ても型が分からない」「インターフェイスで宣言できない」の二つが代表的でしょうか。前者は、Visual Studioを使えばマウスオーバーで型が表示されるので、コーディング上では支障はない。メールやBlogやWikiなど、Visual Studioのサポートのない完全にコードのみの状態だとサッパリなのは確かに難点ではありますが、逆にその程度の部分的な範囲なら、括り出されている目的が明確なわけなので、適切な変数名がついているのなら、正確な型名とまではいかずとも何に使うもののか大体分かるのではないでしょうか?なので、大きな問題だとは私は思いません。もし変数名がテキトーで型名ぐらいしかヒントが得られないんだよ!ということならば、varよりも前にまともな変数名をつけるようにしたほうがいいです。

インターフェイスで宣言できないことは、私は何の問題もないと思っています。具象型やメソッドの返す型でそのまま受けることに何の不都合が?むしろインターフェイスで宣言すると、アップキャスト可能という怪しい状態を作り出しているだけです。

ちなみにintやstring、配列などの基本的なものぐらいは型を書くという流儀もなくはないようですが、それは意味無いのでやめたほうがいいでしょう。

var num = 100;
var text = "hogehoge";
var array = new[] { 1, 2, 3, 4, 5 };

だって、こういうのこそ、見れば一発で分かるほど自明なので。

2. オプション引数を使う(使いすぎない)

害悪もあるわけですが、割と積極的に使ってもいいような気がします。実際Roslyn CTPなどでは結構派手に使われていますし、オーバーロード地獄よりはIntelliSense的にも分かりやすいかな、って。思います。enumなど使うと、明確に何が使われるか見えるんですね、これはとても嬉しくて。やっぱC#としてはIntelliSenseで分かりやすい、というのはとても大事かと。

さて、分かりやすく使いすぎに注意な点としては、引数なしコンストラクタが消滅してしまう可能性があげられます。引数なしコンストラクタがないと、色々なところで弊害が起こります。

// こんなオプション引数なコンストラクタしかないクラスがあるとして
public class ToaruClass
{
    public ToaruClass(int defaultValue = -1)
    {
 
    }
}
 
class Program
{
    static T New<T>() where T : new()
    {
        return new T();
    }
 
    static void Main(string[] args)
    {
        // 使うときは引数なしでnewできるけど
        var _ = new ToaruClass();
 
        // 実態は違うので、ジェネリックのnew制約が不可能になる
        New<ToaruClass>(); // コンパイル通らない
 
        // 引数なしコンストラクタを要求するシリアライザの利用も不可能に
        new XmlSerializer(typeof(ToaruClass)).Serialize();
    }
}

シリアライズできなかったりジェネリックのnew制約がきかなくなってしまったり。ご利用は計画的に。シリアライズに関しては、DataContractSerializerならばコンストラクタを無視するので使えはしますが……。その辺の話はneue cc - .NETの標準シリアライザ(XML/JSON)の使い分けまとめで。

Roslyn CTPのAPIはオプション引数が激しく使われているのですが、中でもこれは面白いと思いました。Mindscape Blog » Blog Archive » In bed with Roslynから引用します。

PropertyDeclarationSyntax newProperty = Syntax.PropertyDeclaration(
    modifiers: Syntax.TokenList(Syntax.Token(SyntaxKind.PublicKeyword)),
    type: node.Type,
    identifier: node.Identifier,
    accessorList: Syntax.AccessorList(
        accessors: Syntax.List(
            Syntax.AccessorDeclaration(
                kind: SyntaxKind.GetAccessorDeclaration,
                bodyOpt: Syntax.Block(
                    statements: Syntax.List(
                        getter
                    )
                )
            ),
            Syntax.AccessorDeclaration(
                kind: SyntaxKind.SetAccessorDeclaration,
                bodyOpt: Syntax.Block(
                    statements: Syntax.List(
                        setter
                    )
                )
            )
        )
    )
);

そう、名前付き引数でツリー作ってるんですね。LINQ to XMLの関数型構築も面白いやり方だと思いましたが、この名前付き引数を使った構築も、かなり素敵です。流行るかも!

3. ジェネリックを使う

もしお持ちの本がArrayListやHashTableを使っているコードが例示されていたら、窓から投げ捨てましょう。Silverlightでは廃止されていますし、WinRT(Windows 8)でも、勿論そんな産廃はありません。もはやどこにもそんなものを使う理由はありません。どうしても何でも入れられるListが欲しければ、List<object>を使えばいいぢゃない。

4. ジェネリックデリゲート(Func, Action)を使う

これも賛否両論ではあるのですが、私は断然ジェネリックデリゲート派です。ちなみにその反対は野良デリゲート量産派でしょうか(悪意のある言い方!)。ジェネリックデリゲートを使うと良い点は、デリゲートの型違い(同じ引数・戻り値のデリゲートでも型が違うとキャストが必要)に悩まされなくてすむ、定義しなくていいので楽、そして、なにより分かりやすい。例えば、「MatchEvaluator」というだけじゃ、何なのかさっぱり分かりません。正規表現のReplaceで使われるデリゲートなのですけどね。Func<Match, string>のほうが、ずっと分かりやすい。

では良くない点は、というと、引数に変数名で意味をつけられない。例えばLINQのSelectメソッドのFunc<TSource, int, TResult>。このintはインデックスですが、そのことはドキュメントコメントからしか分かりません。その点、野良デリゲートを作れば

public delegate TR ConverterWithIndex<T, TR>(T value, int index);

という形で、明示できます。なんて素晴らしい?そう?実のところそんなでもなくて、これ、IntelliSenseからじゃあ分からないんですよね。

F12なりなんなりで型定義まで飛ばないと見えないのです、デリゲートの変数名は。その点Func<Match, string>なら引数の型、戻り値の型がIntelliSenseで見えるわけでして。C#的にはIntelliSenseで見えないと価値は9割減です。というわけで、天秤にかければ、圧倒的にFuncの大勝利。引数ずらずらでイミフになるならドキュメントコメントに書くことで補う、でもいいぢゃない。

ちなみにoutやrefのジェネリックデリゲートは存在しないので、その場合のみ自作デリゲートを立てる必要があります。それ以外、つまるところ99%ぐらいはFunc, Action, EventHandler<T>でいいと思います。LINQだってPredicateじゃなくてFunc<T, bool>だしね。

5. ラムダ式を使う

ラムダ式(C# 3.0)を使わなければ何を使うのって話ですが、ラムダ式の登場により割を食った匿名メソッド(C# 2.0)は産廃です。唯一の利点は、匿名メソッドは引数を使わない場合は省略して書けます。

// 引数省略して書けるぞ!
button.Click += delegate { MessageBox.Show("hoge"); };
// ラムダ式の場合は省略できないんだ(棒)
button.Click += (_, __) => MessageBox.Show("hoge");

こんなことは実にどうでもいいので、匿名メソッドを使うのはやめましょう。もしラムダ式が先にあれば、匿名メソッドはなかったと思います。ジェネリックが最初からあれば非ジェネリックコレクションクラスがなかっただろう、ということな程度には。あとジェネリックが先にあれば野良デリゲートもなかった気がする。なので、多少どうでもいい利点があったとしても、素直に使わないのが一番。

ところでラムダ式の引数の名前ですが、どうしていますか?私は、昔は型名から取っていました、例えばintだったらi、stringだったらs。でも最近は全てxにしています。理由は、面倒くさいし適切な名前が出てこない場合もあるし修正漏れが起こったりする(ハンガリアンみたいなもんですしねえ)などなどで、メリットを感じなかったので。

ちなみに、ラムダ式で長い名前を使うのは反対です。「名前はしっかりつけなきゃダメ!」が原則論のはずなのにxってなんだよそれって感じですが、逆に、小さい範囲のものは小さいほうがいいのです。名前をつけないことで、他の名前のついているものを強調します。なんでもかんでも名前をつけていると五月蝿くて、木を森に隠す、のようになってしまいます。LINQやRxでラムダ式だらけになると、なおそうです。勿論、ラムダ式だからって全てxにするわけではありません。中でネストしてネスト内でも使われたり、式ではなく文になってスコープが長くなっている場合などは、ちゃんと名前をつけます。また、(分かりやすさのため)強く意味を持たせたい場合も名前をつけます。型名以上の意味を持たせられないのなら、あえて名前をつける必要性を感じないのでxです。

そういうわけで、多少崩すこともありますが、原則的に私の命名規則は「ただの変数 = x, 配列などコレクション = xs, 引数を使わない = アンダースコア」としています。xのかわりにアンダースコアを使う流儀もあるようですが、私は嫌いですね……。Scalaのアンダースコアとは意味が違う感じもあるし、同じ.NETファミリーならばF#が引数を使わないという意味でアンダースコアを使っているので、それに合わせたほうがいいと思っています。xだと座標のxと被る、という場合は座標のxにつける変数名をpointXだかpxだかに変えます。

Exceptionはexにしたり、イベントの場合は(sender, e)にしたりはしますけれど、このへんは慣習ですし、わざわざ崩すほうが変かな。あとLINQでのGroupingはgを使ったりしますね。

6. LINQを使う(主にメソッド構文を使う、クエリ構文もたまには使う)

LINQはデータベースのためだけじゃなく、むしろ通常のコレクションへの適用(LINQ to Objects)のほうが多い。そんなにコレクション操作することなんてない、わけがない、はず。

// 配列の中のYから始まるものの名前(スペースできった最後のもの)を取り出す
new[] { "Yamada Tarou", "Yamamoto Jirou", "Suzuki Saburou" }
    .Where(x => x.StartsWith("Y"))
    .Select(x => x.Split(' ').Last());

上の例のような、Where(フィルタリング)+ Select(射影)は特に良く使うパターンです。Pythonなどでもリスト内包表記としてパッケージされるぐらいには。やはり、この手の処理を持っていないと、重苦しい。しかし、C# 3.0はLINQを手にしたので、お陰で軽快に飛び回れるようになりました。しかもただのフィルタ+射影だけではなく、ありとあらゆる汎用コレクション処理を、チェーンで組み合わせることで、無限のパターンを手にしました。

LINQにはメソッド構文とクエリ構文があり、どちらも同じですがメソッド構文のほうが機能豊富だし、分かりやすいです。なのでメソッド構文でメソッドチェーンハァハァしましょう。linq.js - LINQ for JavaScriptで同様の記法でJavaScriptでも使えますし!

じゃあクエリ構文に利点はないのかというと当然そんなことはなく、多重from(SelectManyに変換される)が多く出現する場合はクエリ構文のほうがいいですね。また、Joinなどもクエリ構文のほうが書きやすいし、GroupJoinと合わせた左外部結合を記述したりなど複雑化する場合はクエリ構文じゃないと手に負えません(書けなくはないんですけどねえ)

それと、LINQ to SQLなどExpression Treeをそれぞれの独自プロパイダが解釈するタイプのものは、メソッド構文の豊富な記述可能性が逆に、プロパイダの解釈不能外に飛び出しがちなので、適度に制約の効いたクエリ構文だけで書いたほうがスムーズにいく可能性があります。

また、XMLはC#ではXmlReader/Writer, XmlDocument(DOM), XDocument(LINQ to XML)がありますが、そのうちDOMのXmlDocumentは産廃です。DOMって使いづらいのよね、それにSilverlightにはないし。メモリ内にツリーを持つタイプではXDocument(XElement)でLINQでハァハァするのが主流です。ちなみにXmlReader/Writerはストリーミング型なので別枠、ただ、生で使うことはあまりないと思います。特にWriterは、XStreamingElementを使えば省メモリなストリーミングで、Writeできる、しかもずっと簡単に。なので、使うことはないかと思います。

7. Taskを使う(生スレッドを使わない)

マルチスレッドプログラミングしなきゃ!Threadを使おう?デリゲートのBeginInvokeがある?それともThreadPool?BackgroundWorkerもあるぞ!古い記事はこれらの使用方法が解説されてきました。そうです、今まではそれらしかなかったので。けれど、全部ゴミ箱に投げ捨てましょう。.NET 4.0からは基本的原則的にTaskを使うべきです。豊富な待ち合わせ処理・継続・例外処理・キャンセルなどをサポートしつつ、同じスレッドを使いまわそうとするなど実行効率も配慮されています。もはや生スレッドを使う理由はないし、デリゲートのBeginInvokeなどともさよなら。BackgroundWorkerは、もう少しは出番あるかも(UIへの通知周りが今のTaskだけだと少し面倒、RxやC# 5.0のAsyncなら簡単にこなせるのですが)。

CPUを使う処理を並列に実行をしたいのなら、PLINQやParallel.ForEachなどが手軽かつ超強力です。

また、C# 5.0からはTaskの言語サポートが入り、awaitキーワードによりコード上では待機したように見せかけ同期的のように書けつつ中身は非同期で動く、といったことが可能になります。

async Task<string> GetBingHtml()
{
    var wc = new WebClient();
    var html = await wc.DownloadStringTaskAsync(new Uri("http://bing.com/"));
    return html;
}

awaitするだけで同期的のように非同期が書けるなんて魔法のよう!

また、非同期といっても二つあります。CPUを沢山使って重たい処理と、I/O待ち(ネットワークやファイルアクセス)が重たい処理。これらへの対処は、別です。I/O待ちにスレッドを立てて対処することも可能ではありますが、あまり褒められた話ではありません。と、C#たんが非同期I/O待ちで言ってました。非同期I/Oは優れているのは分かったとしても、記述が面倒なのがネックだったのですね。しかし、C# 5.0からならばawaitが入るのでかなりサクッと書ける。非同期だって、node.jsにばかりは負けてられない!

なお、現在SilverlightやWindows Phone 7にはTaskがない(Silverlight 5にはTask入りました)ですが、将来的には間違いなく入るので、期待して待ちましょう。そして、分かりやすく書けるC# 5.0もwktkして待ちましょう。待ちきればければAsync CTPとして公開されているので、試すことが可能です。

8. Rxを使う

C# 5.0はCTPだし、現実問題として非同期に困ってるんだよ!という場合は、変化球としてReactive Extensionsが使えます。詳しくはReactive Extensions(Rx)入門 - @ITで連載しているので読んでね!第二回がいつまでたっても始まらないのは何故なのでしょう、はい、私が原稿を送っていないからです、ごめんなさい……。これ書いてないで原稿書けやゴルァという感じですはい。いえ、もうすぐもう少しなので、ちょっと待ってください。

RxはTaskとは全く別の次元からやってきつつ、機能的にはある程度代替可能です。C# 5.0が来た時に共存できるのか、というと、非同期面ではTaskに譲るでしょう。けれど、非同期の生成をTaskで行なって、コントロールをawaitも使いつつRxでメインに使うとかも可能です。基本的に、コントロール周りはawaitサポートを除けばRxのほうが強力で柔軟です(代償として効率を若干犠牲にしているけれど)。ただまあ、基本的には非同期処理はTaskに絞られていくだろうと考えています。少し寂しいけど、全体としてより美しく書けるなら全然いいです、むしろ大歓迎なので早くC#5.0来ないかなあ。

ちなみに、Rxは別に非同期のためだけじゃなくて、イベントと、そしてあらゆるソースを合成するという点も見逃せないわけなので、決してAsync来たからRxさようなら、ではないです。その辺のことも連載であうあう。

9. Expressionを使う(そしてEmitしない)

Expressionの簡単な基本ですが、Expressionとして宣言します。Funcとほとんど同じで、違うのは型宣言だけです。

// 同じラムダ式だけれど
Expression<Func<int, int>> expr = x => x * x;
Func<int, int> func = x => x * x;
 
// Expressionで宣言すると実態は以下のものになる(コンパイラが自動生成する)
var paramX = Expression.Parameter(typeof(int), "x");
var expr = Expression.Lambda<Func<int, int>>(
    Expression.Multiply(paramX, paramX),
    new[] { paramX });

Expressionで宣言するとコンパイラがコンパイル時に式木生成コードに変換してくれるのですね。自分で宣言しなくても、メソッドの引数の型がExpressionならば同じです。例えばQueryable.SelectのselectorはExpression型の引数なので、Queryableで連鎖を書いているということは同様に↑のようなコードが吐かれています。

Expressionの仕事は色々ありますが、概ね二つ。式がデータとして取り出せること。簡単な所ではINotifyPropertyChangedの実装なので話題沸騰したりしなかったりした、文字列ではなくプロパティを渡して、そこから引数名を取り出すことができること。

public class MyClass
{
    public string MyProperty { get; set; }
}
 
// これ
public static string GetPropertyName<T>(Expression<Func<T>> propertyExpression)
{
    return (propertyExpression.Body as MemberExpression).Member.Name;
}
 
static void Main(string[] args)
{
    var mc = new MyClass();
    var propName = GetPropertyName(() => mc.MyProperty);
}

こんな形で推し進めたのが、LINQ to SQLなど、式木の塊を解釈してSQLに変換するといった、QueryProviderですね。

そしてもう一つはILビルダー。式木はCompileすることでFuncに変換することが可能です。

// (object target, object value) => ((T)target).memberName = (U)value
static Action<object, object> CreateSetDelegate(Type type, string memberName)
{
    var target = Expression.Parameter(typeof(object), "target");
    var value = Expression.Parameter(typeof(object), "value");
 
    var left =
        Expression.PropertyOrField(
            Expression.Convert(target, type), memberName);
 
    var right = Expression.Convert(value, left.Type);
 
    var lambda = Expression.Lambda<Action<object, object>>(
        Expression.Assign(left, right),
        target, value);
 
    return lambda.Compile();
}
 
// Test
static void Main(string[] args)
{
    var target = new MyClass { MyProperty = 200 };
    var accessor = CreateSetDelegate(typeof(MyClass), "MyProperty");
 
    accessor(target, 1000); // set
    Console.WriteLine(target.MyProperty); // 1000
}

少なくとも、自前でILを書くよりは圧倒的に簡単に、動的コード生成を可能にしました。動的コード生成はCompileは重いものの、一度生成したデリゲートをキャッシュすることで二度目以降は超高速になります。単純なリフレクションよりはずっと速く。といったようなことをneue cc - Expression Treeのこね方・入門編 - 動的にデリゲートを生成してリフレクションを高速化で書いたので読んでね!

10. dynamicを使わない(部分的に使う)

一時期、LLブームで動的言語が持て囃されましたが、今は(静的型付けの)関数型言語ブームで、静的型付けへ寄り戻しが来ています。なので、C#もdynamicあって動的だよねひゃっほーい、なんてことはなく、むしろvarで型推論です(キリッ のほうが正しくて。そんなわけで、dynamicはあまり使いません。ですが、使うとより素敵な場所も幾つかあります。それは、本質的に動的なところに対して。動的なのってどこ?というと、アプリケーションの管理範囲外。

例えばJSONはスキーマレス。DBも、自動生成しなければアプリケーションの外側で見えない。.NETはDLRがあるのでIronPythonなどスクリプト言語との連携などもそう。DynamicJsonを例にだすと、スキーマレスなJSONに対して、そのまま、JSONをJavaScriptで扱うのと同じように使えます。

var json = DynamicJson.Parse(@"{""foo"":""json"", ""bar"":100, ""nest"":{ ""foobar"":true } }");
 
var r1 = json.foo; // "json" - dynamic(string)
var r2 = json.bar; // 100 - dynamic(double)
var r3 = json.nest.foobar; // true - dynamic(bool)

また、動的な存在であるDBへのIDataRecordも、DbExecutorを例に出すと

var products = DbExecutor.ExecuteReaderDynamic(new SqlConnection(connStr), @"
        select ProductName, QuantityPerUnit from Products
        where SupplierID = @SupplierID and UnitPrice > @UnitPrice
        ", new { SupplierID = 1, UnitPrice = 10 })
    .Select(d => new Product
    {
        ProductName = d.ProductName,
        QuantityPerUnit = d.QuantityPerUnit
    })
    .ToArray();

ドットで自然に参照可能なことと、また、dynamicが自動でキャストしてくれるので明示的なキャストが不要なため、取り回しの面倒な手動DBアクセスが随分と簡単になります。(が、DBのアクセス結果を決まったクラスにマッピングするのなら、9で紹介している動的コード生成でアクセサを作ったほうが更に楽々になるのでそこまで出番はないかも。DbExecutorは両方を搭載しているので、必要に応じて選ぶことが可能です)

11. 自動プロパティを使う

自動プロパティ vs パブリックフィールド。同じです。はい、同じです。じゃあパブリックフィールドでいいぢゃん?という話が度々ありますが、いえ、そんなことはありません。プロパティにするとカプセル化が云々、変更した場合に云々、などは割とどうでもいいのですが、重要な違いはちゃんとあります。

WPFとかASP.NETとかのバインディングがプロパティ大前提だったりしてパブリックフィールドだと動かないことがある。

なので、黙って自動プロパティにしておきましょう。それに関連してですが、リフレクションでの扱い易さがプロパティとフィールドでは全然違って、プロパティだと断然楽だったりします。そういう面でサードパーティのライブラリでも、プロパティだけをサポート、なものも割とあるのではかと思います。具体例はあげられませんが私は自分で作るちょろっとリフレクションもにょもにょ系の小粒なライブラリは面倒なのでプロパティだけサポート、にすることが結構あります。

あと自動プロパティの定義はコードスニペットを使って一行でやりましょう。prop->Tab->Tabです。

public int MyProperty { get; set; }

get, setで改行して5行使ったりするのはコードの可読性が落ちるので、好きじゃありません。コードスニペットで一行のものが生成されるわけなので、それに従うという意味でも、一行で書くのがベストと思います。

まとめ

C#は言語がちゃんと進化を続けてきた。進化って無用な複雑化!ではなくて、基本的には今までよくなかった、やりづらいことを改善するために進化するんですよね。だから、素直にその良さを甘受したい。そしてまた、進化するということは、歴史の都合上で廃棄物が出てきてしまうというのもまた隣合わせ。C#は巧妙に、多量の廃棄物が出現するのを避けてきていると思います。ヘジたんの手腕が光ります。しかし、やはりどうしても幾つかの廃棄物に関しては致し方ないところです。それに関しては、ノウハウとして、自分で避けるしかなくて。

この手の話だったら、.NETのクラスライブラリ設計が良いです。もしお持ちでなければ、今すぐ買いましょう!いますぐ!超おすすめです。

また、この手の本だったらEffective C# 4.0もかしら。第一版は(翻訳が出たのが既にC# 4.0の頃で1.0の内容)古くてう~ん、といった感だったのですが、第二版(C# 4.0対応)はかなり良かったです。More Effective C#のほうはLINQ前夜といった感じの内容で若干微妙なのですが、LINQ的な考えが必要な理由を抑える、という点では悪くないかもしれません。また、決定版的な内容を求めるならば、読み通す必要はなく気になるところつまみ読みで良いので、プログラミング.NET Frameworkがお薦めです。

これらに加えて、自分のやりたいことの対象フレームワークの本(ASP.NET/ASP.NET MVC/Win Forms/WPF/WCF/Silverlight/Windows Phone 7/Unity)を一冊用意すれば、導入としては準備万端で素敵ではないかしらん。まあ、今時フレームワークとかの先端の部分だと、フレームワークの進化の速度が速すぎて本だと情報の鮮度が落ちる(特に日本だと)ので、基本は本でサッと抑えて、深い部分はネットの記事を見たほうが良いのではかと思います。ソースコードが公開されていたりフレームワークの制作陣や第一人者が情報出していたりしますしね。本こそが情報の基本にして全て、という時代でもないのだなぁ、と。学習の仕方というのも、時代で変わっていくものだと思います。

Voidインスタンスの作り方、或いはシリアライザとコンストラクタについて

voidといったら、特別扱いされる構造体です。default(void)なんてない。インスタンスは絶対作れない。作れない。本当に?

var v = System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(void));
 
Console.WriteLine(v); // System.Void

作れました。というわけで、GetUninitializedObjectはその名前のとおり、コンストラクタをスルーしてオブジェクトを生成します。そのため、voidですら生成できてしまうわけです、恐ろしい。こないだ.NETの標準シリアライザ(XML/JSON)の使い分けまとめという記事でシリアライザ特集をして少し触れましたが、DataContractSerializerで激しく使われています。よって、シリアライズ対象のクラスがコンストラクタ内で激しく色々なところで作用しているようならば、それが呼び出されることはないので注意が必要です。

ただし、DataContractSerializerを使ったからって、必ずしも呼ばれるわけではないです。DataContract属性がついていなければ普通にコンストラクタを呼ぶ。DataContract属性がついていれば、引数のないコンストラクタがあったとしても、コンストラクタを無視する。という挙動になっているようです。ちょっと紛らわしいので、以下のコードは(参照設定があれば)そのままペーストして動くので、是非試してみてください。

using System;
using System.IO;
using System.Linq.Expressions;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Xml.Serialization;
 
public class EmptyClass
{
    public EmptyClass()
    {
        Console.WriteLine("BANG!");
    }
}
 
[DataContract]
public class ContractEmptyClass
{
    public ContractEmptyClass()
    {
        Console.WriteLine("BANG!BANG!");
    }
}
 
[DataContract]
public class NoEmptyConstructorClass
{
    public NoEmptyConstructorClass(int dummy)
    {
        Console.WriteLine("BANG!BANG!BANG!");
    }
}
 
class Program
{
    static void Main(string[] args)
    {
        // 普通にnewするとBANG!
        Console.WriteLine("New:");
        var e1 = new EmptyClass();
 
        // Activator.CreateInstanceでnewするのもBANG!
        Console.WriteLine("Activator.CreateInstance:");
        var e2 = Activator.CreateInstance<EmptyClass>();
 
        // ExpressionTreeでCompileしてもBANG!
        Console.WriteLine("Expression.New");
        var e3 = Expression.Lambda<Func<EmptyClass>>(Expression.New(typeof(EmptyClass))).Compile().Invoke();
 
        // 何も起こらない(コンストラクタを無視するのでね)
        Console.WriteLine("GetUninitializedObject:");
        var e4 = System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(EmptyClass));
 
        // XmlSerializerでのデシリアライズはBANG!
        Console.WriteLine("XmlSerializer:");
        var e5 = new XmlSerializer(typeof(EmptyClass)).Deserialize(new MemoryStream(Encoding.UTF8.GetBytes("<EmptyClass />")));
 
        // DataContractSerializerでもBANGって起こるよ!
        Console.WriteLine("DataContractSerializer:");
        var e6 = new DataContractSerializer(typeof(EmptyClass)).ReadObject(new MemoryStream(Encoding.UTF8.GetBytes("<EmptyClass xmlns=\"http://schemas.datacontract.org/2004/07/\" />")));
 
        // DataContractJsonSerializerでも起こるんだ!
        Console.WriteLine("DataContractJsonSerializer:");
        var e7 = new DataContractJsonSerializer(typeof(EmptyClass)).ReadObject(new MemoryStream(Encoding.UTF8.GetBytes("{}")));
 
        // DataContract属性をつけたクラスだと何も起こらない
        Console.WriteLine("DataContract + DataContractSerializer:");
        var e8 = new DataContractSerializer(typeof(ContractEmptyClass)).ReadObject(new MemoryStream(Encoding.UTF8.GetBytes("<ContractEmptyClass xmlns=\"http://schemas.datacontract.org/2004/07/\" />")));
 
        // DataContract属性をつけたクラスだとJsonSerializerのほうも当然何も起こらない
        Console.WriteLine("DataContract + DataContractJsonSerializer:");
        var e9 = new DataContractJsonSerializer(typeof(ContractEmptyClass)).ReadObject(new MemoryStream(Encoding.UTF8.GetBytes("{}")));
 
        // 空コンストラクタのないもの+DataContractSerializerだと何も起こらない
        Console.WriteLine("NoEmptyConstructor + DataContractSerializer:");
        var e10 = new DataContractSerializer(typeof(NoEmptyConstructorClass)).ReadObject(new MemoryStream(Encoding.UTF8.GetBytes("<NoEmptyConstructorClass xmlns=\"http://schemas.datacontract.org/2004/07/\" />")));
 
        // 空コンストラクタのないもの+DataContractJsonSerializerでも何も起こらない
        Console.WriteLine("NoEmptyConstructor + DataContractJsonSerializer:");
        var e11 = new DataContractJsonSerializer(typeof(NoEmptyConstructorClass)).ReadObject(new MemoryStream(Encoding.UTF8.GetBytes("{}")));
    }
}

.NET 4でもSilverlightでも共通です。この挙動は妥当だと思います。DataContract属性を付けた時点で、そのクラスはシリアライズに関して特別な意識を持つ必要がある。コンストラクタ内でシリアライズで復元できない副作用のある処理をすべきではない。逆に、何も付いていない場合は特に意識しなくても大丈夫。

.NETの標準シリアライザ(XML/JSON)の使い分けまとめ

今年もAdvent Calendarの季節がやってきましたね。去年は私はC#とJavaScriptで書きましたが、今年はC#とSilverlightでやります。というわけで、この記事はSilverlight Advent Calendar 2011用のエントリです。前日は@posauneさんのSilverlightのListBoxでつくるいんちきHorizontalTextBlock でした。

今回の記事中のサンプルはSilverlight 4で書いています。が、Silverlight用という体裁を持つためにDebug.WriteLineで書いているというだけで、Silverlightらしさは皆無です!えー。.NET 4でもWindows Phone 7でも関係なく通じる話ですねん。

シリアライザを使う場面

概ね3つではないでしょうか。外部で公開されているデータ(APIをネット経由で叩くとか)をクラスに変換する。これは 自分の管理外→プログラム での片方向です。内部で持っているデータ(クラスのインスタンス)を保存用・復元用に相互変換する。これは プログラム←→自分の管理内 での双方向です。最後に、内部で持っているデータを公開用に変換する。これは プログラム→外部 での片方向。

目的に応じてベストな選択は変わってきます。こっから延々と長ったらしいので、まず先に結論のほうを。

  • 外部APIを叩く→XML/XmlSerializer, JSON/DataContractJsonSerializer
  • オブジェクトの保存・復元用→DataContractSerializer
  • 外部公開→さあ?

外部公開のは、Silverlightの話じゃないので今回はスルーだ!XStreamingElementで組み上げてもいいし、何でもいいよ!WCFのテンプレにでも従えばいいんぢゃないでしょーか。

XmlSerializer

古くからあるので、シリアライザといったらこれ!という印象な方も多いのではないでしょうか。その名の通り、素直にXMLの相互変換をしてくれます。

// こんなクラスがあるとして
// (以降、断り書きなくPersonが出てきたらこいつを使ってると思ってください)
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
// データ準備
var data = new Person { Name = "山本山", Age = 99 };
 
var serializer = new XmlSerializer(typeof(Person));
using (var ms = new MemoryStream())
{
    serializer.Serialize(ms, data); // シリアライズ
 
    // 結果確認出力
    var xml = Encoding.UTF8.GetString(ms.ToArray(), 0, (int)ms.Length);
    Debug.WriteLine(xml);
 
    ms.Position = 0; // 巻き戻して……
    var value = (Person)serializer.Deserialize(ms); // デシリアライズ
    Debug.WriteLine(value.Name + ":" + value.Age); // 山本山:99
}
// 出力結果のXML
<?xml version="1.0" encoding="utf-8"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Name>山本山</Name>
  <Age>99</Age>
</Person>

素直な使い勝手、素直な出力。いいですね。さて、しかし特に外部APIを叩いて手に入るXMLは名前PascalCaseじゃねーよ、とか属性の場合どうすんだよ、という場合も多いでしょう。細かい制御にはXmlAttributeを使います。

[XmlRoot("people")]
public class People
{
    [XmlElement("count")]
    public int Count { get; set; }
    [XmlArray("persons")]
    [XmlArrayItem("person")]
    public Person[] Persons { get; set; }
}
 
[XmlRoot("person")]
public class Person
{
    [XmlElement("name")]
    public string Name { get; set; }
    [XmlAttribute("age")]
    public int Age { get; set; }
}
// データ準備
var data = new People
{
    Count = 2,
    Persons = new[]
{
    new Person { Name = "山本山", Age = 99 },
    new Person { Name = "トマト", Age = 19 }
}
};
var xml = @"
    <people>
        <count>2</count>
        <persons>
            <person age=""14"">
                <name>ほむ</name>
            </person>
            <person age=""999"">
                <name>いか</name>
            </person>
        </persons>
    </people>";
 
var serializer = new XmlSerializer(typeof(People));
 
// シリアライズ
using (var ms = new MemoryStream())
{
    serializer.Serialize(ms, data);
    Debug.WriteLine(Encoding.UTF8.GetString(ms.ToArray(), 0, (int)ms.Length));
}
 
// デシリアライズ
using (var sr = new StringReader(xml))
{
    var value = (People)serializer.Deserialize(sr);
    foreach (var item in value.Persons)
    {
        Debug.WriteLine(item.Name + ":" + item.Age);
    }
}
 
// 出力結果のXMLは↑に書いたXMLと同じようなものなので割愛

ちょっと属性制御が面倒ですが、それなりに分かりやすく書けます。他によく使うのは無視して欲しいプロパティを指定するXmlIgnoreかしら。さて、そんな便利なXmlSerializerですが、XML化するクラスに制限があります。有名所ではDictionaryがシリアライズできねえええええ!とか。小細工して回避することは一応可能ですが、そんな無理するぐらいなら使うのやめたほうがいいでしょう、シリアライザは別にXmlSerializerだけじゃないのだから。

というわけで、XmlSerializerの利用シーンのお薦めは、ネットワークから外部APIを叩いて手に入るXMLをクラスにマッピングするところです。柔軟な属性制御により、マッピングできないケースは(多分)ないでしょう。いや、分かりませんが。まあ、ほとんどのケースでは大丈夫でしょう!しかし、LINQ to XMLの登場により、手書きで変換するのも十分お手軽なってしまったので、こうして分かりにくい属性制御するぐらいならXElement使うよ、というケースのほうが多いかもしれません。結局、XML構造をそのまま映すことしかできないので、より細かく変換できたほうが良い場合もずっとあって。

実際、私はもう長いことXmlSerializer使ってない感じ。LINQ to XMLは偉大。

DataContractSerializer

割と新顔ですが、もう十分古株と言ってよいでしょう(どっちだよ)。XmlSerializerと同じくオブジェクトをXMLに変換するのですが、その機能はずっと強力です。Dictionaryだってなんだってシリアライズできますよ、というわけで、現在では.NETの標準シリアライザはこいつです。

// データ準備
var data = new Person { Name = "山本山", Age = 99 };
 
var serializer = new DataContractSerializer(typeof(Person));
using (var ms = new MemoryStream())
{
    serializer.WriteObject(ms, data); // シリアライズ
 
    // 結果確認出力
    var xml = Encoding.UTF8.GetString(ms.ToArray(), 0, (int)ms.Length);
    Debug.WriteLine(xml);
 
    ms.Position = 0; // 巻き戻して……
    var value = (Person)serializer.ReadObject(ms); // デシリアライズ
    Debug.WriteLine(value.Name + ":" + value.Age); // 山本山:99
}
<Person xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/SilverlightApplication34"><Age>99</Age><Name>山本山</Name></Person>

とまあ、使い勝手はXmlSerializerと似たようなものです。おお、出力されるXMLは整形されていません。整形して出力したい場合は

// 出力を整形したい場合はXmlWriter/XmlWriterSettingsを挟む
using (var ms = new MemoryStream())
using (var xw = XmlWriter.Create(ms, new XmlWriterSettings { Indent = true }))
{
    serializer.WriteObject(xw, data);
 
    xw.Flush();
    var xml = Encoding.UTF8.GetString(ms.ToArray(), 0, (int)ms.Length);
    Debug.WriteLine(xml);
}
<?xml version="1.0" encoding="utf-8"?>
<Person xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/SilverlightApplication34">
  <Age>99</Age>
  <Name>山本山</Name>
</Person>

さて、結果をXmlSerializerと見比べてみるとどうでしょう。名前空間が違います。SilverlightApplication34ってありますね。これは、私がこのXMLを出力するのに使ったSilverlightプロジェクトの名前空間です。ワシのConsoleApplicationは221まであるぞ(整理しろ)。さて、ではこのXMLをデシリアライズするのに、別のアプリケーション・別のクラスで使ってみるとどうでしょう?

namespace TestSilverlightApp
{
    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
 
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
 
            var xml = @"<?xml version=""1.0"" encoding=""utf-8""?>
            <Person xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"" xmlns=""http://schemas.datacontract.org/2004/07/SilverlightApplication34"">
                <Age>99</Age>
                <Name>山本山</Name>
            </Person>";
 
            var serializer = new DataContractSerializer(typeof(Person));
            using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
            {
                // System.Runtime.Serialization.SerializationExceptionが起こってデシリアライズできない
                // 名前空間 'http://schemas.datacontract.org/2004/07/TestSilverlightApp' の要素 'Person' が必要です。
                // 名前が 'Person' で名前空間が 'http://schemas.datacontract.org/2004/07/SilverlightApplication34' の 'Element' が検出されました。
                var value = (Person)serializer.ReadObject(ms);
            }
        }
    }
}

デシリアライズ出来ません。対象オブジェクトが名前空間によって厳密に区別されるからです。じゃあどうするのよ!というと、属性で名前空間を空、という指示を与えます。

// DataContract属性をクラスにつけた場合は
// そのクラス内のDataMember属性をつけていないプロパティは無視される
[DataContract(Namespace = "", Name = "person")]
public class Person
{
    [DataMember(Name = "name")]
    public string Name { get; set; }
    [DataMember(Name = "age")]
    public int Age { get; set; }
}
// こんなプレーンなXMLも読み込める
var xml = @"
    <person>
        <age>99</age>
        <name>山本山</name>
    </person>";
 
var serializer = new DataContractSerializer(typeof(Person));
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
{
    var value = (Person)serializer.ReadObject(ms);
    Debug.WriteLine(value.Name + ":" + value.Age);
}

属性面倒くせー、ですけれど、まあしょうがない。そうすれば外部からのXMLも読み込めるし、と思っていた時もありました。以下のようなケースではどうなるでしょうか?Personクラスは↑のものを使うとして。

// こんなさっきと少しだけ違うXMLがあるとして
var xml = @"
    <person>
        <name>山本山</name>
        <age>99</age>
    </person>";
 
var serializer = new DataContractSerializer(typeof(Person));
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
{
    var value = (Person)serializer.ReadObject(ms);
    Debug.WriteLine(value.Name + ":" + value.Age); // 結果は???
}

これは出力結果は「山本山:0」になります。Ageが0、つまり復元されませんでした。なぜかというと、XMLを見てください。nameが先で、ageが、後。DataContractSerializerは規程された順序に強く従います。DataMember属性のOrderプロパティで順序を与えるか、与えない場合はアルファベット順(つまりAgeが先でNameが後)となります。この辺はデータ メンバーの順序に書かれています。

と、いうような事情から、DataContractSerializerを外部XMLからの受け取りに使うのはお薦めしません。XmlSerializerなら順序無視なので大丈夫です。いや、普通は順序が変わったりなどしないだろう!と思わなくもなくもないけれど、意外とデタラメなのじゃないか、基本的にはお外からのデータが何もかも信用できるわけなどないのだ、とうがってしまい(TwitterのAPIとか胡散臭さいのを日常的に触っていると!)、厳しいかなって、思ってしまうのです。

しかし、オブジェクトの保存・復元用にはDataContractSerializerは無類の強さを発揮します。例えば設定用のクラスを丸ごとシリアライズ・デシリアライズとかね。iniにして、じゃなくてフツーはXMLにすると思いますが、それです、それ。Dictionaryだってシリアライズできるし、引数なしコントラクタがないクラスだってシリアライズできちゃうんですよ?

// とある引数なしコンストラクタがないクラス
[DataContract]
public class ToaruClass
{
    [DataMember]
    public string Name { get; set; }
 
    public ToaruClass(string name)
    {
        Name = name;
    }
}
var toaru = new ToaruClass("たこやき");
 
var serializer = new DataContractSerializer(typeof(ToaruClass));
using (var ms = new MemoryStream())
{
    serializer.WriteObject(ms, toaru); // シリアライズできるし
 
    ms.Position = 0;
    var value = (ToaruClass)serializer.ReadObject(ms); // デシリアライズできる
 
    Debug.WriteLine(value.Name); // たこやき
}

ただし、対象クラスにDataContract属性をつけてあげる必要はあります。つけてないとシリアライズもデシリアライズもできません。

ちなみに何でコンストラクタがないのにインスタンス化出来るんだよ!というと、System.Runtime.Serialization.FormatterServices.GetUninitializedObjectを使ってインスタンス化しているからです(Silverlightの場合はアクセス不可能)。こいつはコンストラクタをスルーしてオブジェクトを生成する反則スレスレな存在です、というか反則です。チートであるがゆえに、対象クラスにはDataContract属性をつける必要があります。コンストラクタ無視してもいいよ、ということを保証してあげないとおっかない、というわけです。(GetUninitializedObjectメソッド自体は別に属性は不要で何でもインスタンス化できます、typeof(void)ですらインスタンス化できます、無茶苦茶である)

なお、このGetUninitializedObjectが使われるのはDataContract属性がついているクラスのみです。DataContract属性がついていなければ、普通のコンストラクタが呼ばれるし、逆にDataContract属性がついていると、例え引数をうけないコンストラクタがあったとしても、GetUninitializedObject経由となりコンストラクタは無視されます。DataContract属性を付ける時はコンストラクタ内でシリアライズで復元できない副作用のある処理をすべきではない。ということに注意してください。

また、.NET 4版ではprivateプロパティの値も復元できるのですが、Silverlightの場合は無理のようです。ということでフル.NETなら不変オブジェクトでもサクサク大勝利、と思ってたのですが、Silverlightでの不変オブジェクトのシリアライズ・デシリアライズは不可能のようです。保存したいなら、保存専用の代理のオブジェクトを立ててやるしかない感じでしょうかね。

そんなわけで微妙な点も若干残りはしますが、オブジェクトを保存するのにはDataContractSerializerがお薦めです。

DataContractとSerializable

シリアライズ可能なクラス、の意味でDataContract属性をつけているわけですが、じゃあSerializable属性は?というと、えーと、SerializableはSilverlightでは入っていなかったりするとおり、過去の遺物ですね。なかったということで気にしないようにしましょう。

DataContractJsonSerializer

今時の言語はJSONが簡単に扱えなきゃダメです。XMLだけ扱えればいい、なんて時代は過ぎ去りました。しかしC#は悲しいことに標準では……。いや、いや、SilverlightにはSystem.Jsonがありますね。しかし.NET 4にはありません(.NET 4.5とWinRTには入ります)。いや、しかし.NET 4にはDynamicJsonがあります(それ出していいならJSON.NETがあるよ、で終わりなんですけどね)。が、Windows Phone 7には何もありません。ああ……。

とはいえ、シリアライザならば用意されています。DataContractJsonSerializerです。

// データ準備
var data = new Person { Name = "山本山", Age = 99 };
 
var serializer = new DataContractJsonSerializer(typeof(Person));
using (var ms = new MemoryStream())
{
    serializer.WriteObject(ms, data); // シリアライズ
 
    // 結果確認出力
    var xml = Encoding.UTF8.GetString(ms.ToArray(), 0, (int)ms.Length);
    Debug.WriteLine(xml); // {"Age":99,"Name":"山本山"}
 
    ms.Position = 0; // 巻き戻して……
    var value = (Person)serializer.ReadObject(ms); // デシリアライズ
    Debug.WriteLine(value.Name + ":" + value.Age); // 山本山:99
}

使い勝手はDataContractSerializerと完全に一緒です。ただし、違う点が幾つか。名前空間が(そもそもJSONで表現不可能なので)なくなったのと、順序も関係なく復元可能です。

var json1 = @"{""Name"":""山本山"",""Age"":99}";
var json2 = @"{""Age"":99,""Name"":""山本山""}";
 
var serializer = new DataContractJsonSerializer(typeof(Person));
using (var ms1 = new MemoryStream(Encoding.UTF8.GetBytes(json1)))
using (var ms2 = new MemoryStream(Encoding.UTF8.GetBytes(json2)))
{
    var value1 = (Person)serializer.ReadObject(ms1);
    var value2 = (Person)serializer.ReadObject(ms2);
 
    Debug.WriteLine(value1.Name + ":" + value2.Age);
    Debug.WriteLine(value2.Name + ":" + value2.Age);
}

というわけで、随分とDataContractSerializerよりも使い勝手が良い模様。いい話だなー。さて、難点は出力されるJSONの整形が不可能です。DataContractSerializerではXmlWriterSettingsで行えましたが、DataContractJsonSerializerではそれに相当するものがありません。というわけでヒューマンリーダブルな形で出力、とはならず、一行にドバーっとまとめて吐かれるのでかなり苦しい。

もう一つ、これは本当に大したことない差なのでどうでもいいのですが、DataContractSerializerのほうが速いです。理由は単純でDataContractSerializerに一枚被せる形でDataContractJsonSerializerが実装されているから。その辺の絡みで.NET 4にはJsonReaderWriterFactoryなどがあって、これを直に触ってJSON→XML変換をするとLINQ to XMLを通したJSONの直接操作が標準ライブラリのみで可能なのですが、Silverlight/Windows Phone 7では残念なことに触ることができません。

外部APIを叩いて変換する際に、シリアライズはお手軽で便利であると同時に、完全に同一の形のオブジェクトを用意しなければならなくて、かったるい側面もあります。LINQ to XML慣れしていると特に。そういった形でJSONを扱いたい場合、WP7ではJson.NETを使う、しかありません。使えばいいんぢゃないかな、どうせNuGetでサクッと入れられるのだし。

とはいえまあ、そう言うほど使いづらいわけでもないので、標準のみでJSONを扱いたいという場合は、DataContractJsonSerializerが第一にして唯一の選択肢になります。

JavaScriptSerializer

.NET Framework 4.0 Client Profileでは使えないのですが、FullならばSystem.Web.Extensionを参照することでJavaScriptSerializerが使えます。もはや完全にSilverlightと関係ないのでアレですが、少し見てみましょう。

var serializer = new JavaScriptSerializer();
 
var target = new { Name = "ほむほむ", Age = 14 };
var json = serializer.Serialize(target); // stringを返す

Serializeで文字列としてのJSONを返す、というのがポイントです。それと、シリアライザ作成時にtypeを指定しません。また、匿名型もJSON化することが可能です(これはDataContractSerializerでは絶対無理)。ただし、コンストラクタのないクラスのデシリアライズは不可能です。

中々使い勝手がいいですね!で、これは、リフレクションベースの非常に素朴な実装です。だから匿名型でもOKなんですねー。ちょっとした用途には非常に楽なのですが、Client Profileでは使えないこともありますし(ASP.NETで使うために用意されてる)、あまり積極的に使うべきものではないと思います。ちなみに、一時期ではObsoleteになっていてDataContractJsonSerializer使え、と出ていたのですが、またObsoleteが外され普通に使えるようになりました。やはり標準シリアライザとしてはDataContractJsonSerializerだけだと重すぎる、ということでしょうか。

バイナリとか

別にシリアライズってXMLやJSONだけじゃあないのですね。サードパーティ製に目を向ければ、色々なものがあります。特に私がお薦めなのはprotobuf-net。これはGoogleが公開しているProtocol Buffersという仕様を.NETで実装したものなのですが、とにかく速い。めちゃくちゃ速い。稀代のILマスターが書いているだけある恐ろしい出来栄えです。SilverlightやWP7版もあるので、Protocol Buffersの本来の用途というだけなく、幅広く使えるのではかとも思います。

もう一つは国内だと最近目にすることの多いMessagePack。以前に.NET(C#)におけるシリアライザのパフォーマンス比較を書いたときは振るわないスコアでしたが、最近別のC#実装が公開されまして、それは作者によるベンチMessagePack for .NET (C#) を書いたによると、protobuf-netよりも速いそうです。

Next

というわけでSilverlight枠でいいのか怪しかったですが、シリアライザの話でした。次は@ugaya40さんのWeakEventの話です。引き続きチェックを。あ、あと、Silverlight Advent Calendarはまだ埋まってない(!)ので、是非是非参加して、埋めてやってください。申し込みはSilverlight Advent Calendar 2011から。皆さんのエントリ、待ってます。どうやらちょうど今日Silverlight 5がリリースされたようなので、SL5の新機能ネタとかいいんじゃないでしょうか。

自家製拡張メソッド制作のすすめ だいx回 BufferWithPadding

Ix(Interactive Extensions)は使っていますか?Rxから逆移植されてきている(IxのNuGet上のアイコンはRxのアイコンの逆向きなのですね)、LINQ to Objectsを更に拡張するメソッド群です。みんな大好きForEachなど、色々入っています。その中でも、私はBufferというものをよく使っています。Ixが参照できない場合は何度も何度も自作するぐらいに使いどころいっぱいあって、便利です。こんなの。

// 指定個数分をまとめたIList<T>を返します
// 第二引数を使うとずらす個数を指定することもできます
// これの結果は
// 0123
// 4567
// 89
foreach (var xs in Enumerable.Range(0, 10).Buffer(4))
{
    xs.ForEach(Console.Write);
    Console.WriteLine();
}

標準でこういうのできないのー?というと、できないんですよねえ、残念なことに。

さて、ところで、この場合、指定個数に足りなかった場合はその分縮められたものが帰ってきます。上の例だと返ってくるListの長さは4, 4, 2でした。でも、埋めて欲しい場合ってあります。足りない分は0で埋めて長さは4, 4, 4であって欲しい、と。そこはLINQなので、創意工夫で頑張りましょう。例えば

// EnumerableEx.Repeatは指定の値の無限リピート
// それと結合して、Takeで詰めることで足りない場合だけ右を埋めることが出来る
// 0123
// 4567
// 8900
foreach (var xs in Enumerable.Range(0, 10).Buffer(4))
{
    xs.Concat(EnumerableEx.Repeat(0)).Take(4).ForEach(Console.Write);
    Console.WriteLine();
}

EnumerableEx.RepeatはIxにある無限リピート。Ixを参照しない場合は Enumerable.Repeat(value, int.MaxValue) で代用することも一応可能です。

さて、しかしこれも面倒なので、自家製拡張メソッドを作りましょう。拡張メソッドはばんばん作るべきなのです。

// 指定した値で埋めるように。これの結果は
// 0123
// 4567
// 89-1-1
foreach (var xs in Enumerable.Range(0, 10).BufferWithPadding(4, -1))
{
    xs.ForEach(Console.Write);
    Console.WriteLine();
}
 
public static class EnumerableExtensions
{
    public static IEnumerable<T[]> BufferWithPadding<T>(this IEnumerable<T> source, int count, T paddingValue = default(T))
    {
        if (source == null) throw new ArgumentNullException("source");
        if (count <= 0) throw new ArgumentOutOfRangeException("count");
 
        return BufferWithPaddingCore(source, count, paddingValue);
    }
 
    static IEnumerable<T[]> BufferWithPaddingCore<T>(this IEnumerable<T> source, int count, T paddingValue)
    {
        var buffer = new T[count];
        var index = 0;
        foreach (var item in source)
        {
            buffer[index++] = item;
            if (index == count)
            {
                yield return buffer;
                index = 0;
                buffer = new T[count];
            }
        }
 
        if (index != 0)
        {
            for (; index < count; index++)
            {
                buffer[index] = paddingValue;
            }
            yield return buffer;
        }
    }
}

すっきりしますね!Emptyの時は何も列挙しないようにしていますが、Emptyの時は埋めたのを一つ欲しい、と思う場合は最後のifの囲みを外せばOK。あと、最後のif…for…yieldの部分を var dest = new T[index]; Array.Copy(buffer, dest, index); yield return dest; に変えればパディングしないBufferになります。Ix参照したくないけどBuffer欲しいなあ、と思ったときにコピペってどうぞ。

本体のコードと引数チェックを分けているのは、yield returnは本体が丸ごと遅延評価されるため、引数チェックのタイミング的によろしくないからです。少し面倒ですが、分割するのが良い書き方。詳しくはneue cc - 詳説Ix Share/Memoize/Publish編(もしくはyield returnの注意点)で書いていますので見てください。

Reactive Extensionsとスレッドのlock

ぱられるぱられる。もしパラレルにイベントが飛んできたら、どうする?

public class TestParallel
{
    public event Action<int> Log = _ => { }; // nullチェック面倒ぃので
 
    public void Raise()
    {
        // デュアルコア以上のマシンで試してね!
        Parallel.For(0, 10000000, x =>
        {
            Log(x);
        });
    }
}
 
class Program
{
    static void Main(string[] args)
    {
        var list = new List<int>();
        var tes = new TestParallel();
 
        // イベント登録して
        tes.Log += x => list.Add(x);
 
        // 実行
        tes.Raise();
    }
}

これは、十中八九、例外が出ます。list.Addはスレッドセーフじゃないので、まあそうだよね、と。では、Rxを使ってみるとどうなるでしょうか。

var list = new List<int>();
var tes = new TestParallel();
 
// イベント登録して
Observable.FromEvent<int>(h => tes.Log += h, h => tes.Log -= h)
    .Subscribe(list.Add);
 
// 実行
tes.Raise();

やはり変わりません。例外出ます。FromEventを中継しているだけですから……。さて、しかし一々Addの手前でlockするのは面倒だ、と、そこでSynchronizeメソッドが使えます。

Observable.FromEvent<int>(h => tes.Log += h, h => tes.Log -= h)
    .Synchronize()
    .Subscribe(list.Add);
 
// ようするにこんな感じになってる
 
var gate = new Object();
//....
lock(gate)
{
    OnNext();
}

これで、list.Addを問題なく動作させられます。Listとか適度にデリケートなので適当に注意してあげましょう。

Subjectの場合

さて、上のはイベントでしたが、ではSubjectの場合はどうなるでしょう。

public class TestParallel
{
    Subject<int> logMessenger = new Subject<int>();
    public IObservable<int> Log { get { return logMessenger.AsObservable(); } }
 
    public void Raise()
    {
        // デュアルコア以上のマシンで試してね!
        Parallel.For(0, 10000000, x =>
        {
            logMessenger.OnNext(x);
        });
    }
}
 
class Program
{
    static void Main(string[] args)
    {
        var list = new List<int>();
        var tes = new TestParallel();
 
        // イベント登録して
        tes.Log.Subscribe(list.Add);
 
        // 実行
        tes.Raise();
    }
}

たまーに例外起こらず処理できることもあるんですが、まあ大体は例外起こるんじゃないかと思います。初期のRxのSubjectは割とガチガチにlockされてたのですが、現在はパフォーマンスが優先されているため挙動が変更され、ゆるゆるです。回避策は同様にSynchronizeを足すことです。

tes.Log.Synchronize().Subscribe(list.Add);

これで問題なし。

余談

手元に残っていた大昔のRxを使って実行してみたら、死ぬほど遅かったり。確実に現在のものはパフォーマンス上がっていますねえ。あと、なんかもう最近面倒でeventだからってEventArgs使わなきゃならないなんて誰が言ったー、とActionばかり使うという手抜きをしてます。だってsenderいらないもん、大抵のばやい。

ReactiveProperty ver 0.3.0.0 - MとVMのバインディングという捉え方

今回の更新よりアイコンが付きました。専用のアイコンがあると、とっても本格的な感じがしますねー。色はRxにあわせて紫-赤紫。デザインは私の好みな幾何学的な感じです。@ocazucoさんに作って頂きました、ありがとうございます!色々ワガママ言ってお手数かけました。

ReactiveProperty - MVVM Extensions for Rx - ver 0.3.0.0

Rxとは何か、というとIObservable<T>と「見なせる」ものを合成するためのライブラリです。だから、見なせるものさえ見つかれば、活躍の幅は広がっていく。ReactivePropertyは色々なものを、そのように「見なして」いくことで、RxでOrchestrateできる幅をドラスティックに広げます。土台にさえ乗せてしまえば、あとはRxにお任せ。その場合に大切なのは、土台に乗せられるよう、閉じないことです。しかし、もし閉じているのなら、開くための鍵を提供します。

デフォルトモード変更

ReactivePropertyのデフォルトモードが DistinctUntilChanged|RaiseLatestValueOnSubscribe になりました。今まではRaise…が入ってなかったのですが、思うところあって変わりました。例えばCombineLatestは、全てが一度は発火していないと動き出しません。ReactiveCommandの条件に使うなどの場合にRaiseしてくれないと不都合極まりなく、かつ、Subscribeと同時にRaiseすることによる不都合なシーンは逆に少ない。ことを考えると、必然的にデフォルトをどちらに振るべきかは、分かりきった話でした。

そのことは0.1の時、サンプル作りながら思ってたんですが悩んだ末に、省いちゃったんですねえ。RaiseLatestValueOnSubscribeが入ると不便なシーンもある(initialValueを設定しないとまず最初にnullが飛んでいくとか)ので、どちらを取るかは悩ましいところではあるんですが、シチュエーションに応じて最適なほうを選んでください、としか言いようがないところです。

ToReactivePropertyAsSynchronized

長い。メソッド名が。

これは何かというとINotifyPropertyChanged->ReactiveProperty変換です。今までもObservePropertyメソッド経由で変換できましたが、それは一度IObservable<T>に変換するため、Model→ReactivePropertyという一方向のPushでしかありませんでした。Two-wayでのバインドで値の同期を取りたい場合は、今回から搭載されたToReactivePropertyAsSynchronizedを使ってください。

// こんな通知付きモデルがあるとして
public class ObservableObject : INotifyPropertyChanged
{
    private string name;
    public string Name
    {
        get { return name; }
        set
        {
            name = value;
            PropertyChanged(this, new PropertyChangedEventArgs("Name"));
        }
    }
 
    public event PropertyChangedEventHandler PropertyChanged = (_, __) => { };
}
 
// それを使ったViewModelを作るなら
public class TwoWayViewModel
{
    public ReactiveProperty<string> OneWay { get; private set; }
    public ReactiveProperty<string> TwoWay { get; private set; }
 
    public TwoWayViewModel()
    {
        var inpc = new ObservableObject { Name = "ヤマダ" };
 
        // ObservePropertyを使うとIObservable<T>に変換できます
        // ラムダ式でプロパティを指定するので、完全にタイプセーフです
        // それをToReactivePropertyすればOneWayで同期したReactivePropertyになります
        OneWay = inpc.ObserveProperty(x => x.Name).ToReactiveProperty();
 
        // ToReactivePropertyAsSynchronizedで双方向に同期することができます
        TwoWay = inpc.ToReactivePropertyAsSynchronized(x => x.Name);
    }
}

INotifyProeprtyChangedなModelをReactivePropertyなViewModelに持っていきたい時などに、使いやすいのではと思います。また、同期する型が異なっていても対応することができます。コンバーターのようにconvertとconvertBackを指定してください。

ReactiveProperty.FromObject

こちらもToReactivePropertyの亜種ですが、ReactiveProperty→Modelというソース方向への片方向の同期を取ります。ModelはINotifyPropertyChangedである必要はありません。

// こんなただのクラスがあったとして
public class PlainObject
{
    public string Name { get; set; }
}
 
// それと同期させたいとき
public class OneWayToSourceViewModel
{
    public ReactiveProperty<string> OneWayToSource { get; private set; }
 
    public OneWayToSourceViewModel()
    {
        var poco = new PlainObject { Name = "ヤマダ" };
 
        // ReactiveProperty.FromObjectで変換することができます
        // この場合、ReactiveProperty -> Objectの方向のみ値が流れます
        OneWayToSource = ReactiveProperty.FromObject(poco, x => x.Name);
    }
}

片方向の同期が定型的な局面、例えば設定クラスなんかは通知は必要ないと思うのですが、それをUIから一方向で値を投影したい場合に、これを使うことで楽になると思います。

また、Sampleにこれら3つの解説を追加しましたので、実際にどう反映されるのか、動きを確認したい場合はそちらを見てください。

CombineLatestValuesAreAllTrue

長い。メソッド名が。これはReactive Extensionsお題 - かずきのBlog@Hatenaに書かれているもので、使うシーンよくありそうな頻出パターンになりそうだと思ったので、お借りすることにしました。ありがとうございます。使い方を見てもらったほうが速いので、まず例を。

Microsoft Silverlight を入手

<StackPanel>
    <StackPanel Orientation="Horizontal">
        <CheckBox IsChecked="{Binding IsCheckedA.Value, Mode=TwoWay}">Check A</CheckBox>
        <CheckBox IsChecked="{Binding IsCheckedB.Value, Mode=TwoWay}">Check B</CheckBox>
        <CheckBox IsChecked="{Binding IsCheckedC.Value, Mode=TwoWay}">Check C</CheckBox>
    </StackPanel>
    <Button Command="{Binding ExecCommand}">全部チェックで押せる</Button>
</StackPanel>
// using Codeplex.Reactive.Extensions; (これを忘れないように)
 
public class MainPageViewModel
{
    public ReactiveProperty<bool> IsCheckedA { get; private set; }
    public ReactiveProperty<bool> IsCheckedB { get; private set; }
    public ReactiveProperty<bool> IsCheckedC { get; private set; }
    public ReactiveCommand ExecCommand { get; private set; }
 
    public MainPageViewModel()
    {
        IsCheckedA = new ReactiveProperty<bool>();
        IsCheckedB = new ReactiveProperty<bool>();
        IsCheckedC = new ReactiveProperty<bool>();
 
        ExecCommand = new[] { IsCheckedA, IsCheckedB, IsCheckedC }
            .CombineLatestValuesAreAllTrue()
            .ToReactiveCommand();
 
        ExecCommand.Subscribe(_ => MessageBox.Show("しんぷる!"));
    }
}

3つのチェックボックスが全てONなら実行可能なコマンドを作る、です。こんな風に、全てがtrueの時、といった集約をしたい場合に便利に使うことができます。プレゼンテーションロジック、に該当する部分だと思いますが、ここでもRxは十分以上に活躍できます。また、外部からCanExecuteChangedをぶっ叩くようなカオティックなこともしません、ReactiveCommandならね。

ReactiveTimer

Timerです。.NETはTimerが山のようにあります。Threading.Timer, Timers.Timer, Forms.Timer, DispatcherTimer, Observable.Timer。ここにまたReactiveTimerという新たなるTimerが誕生し、人類を混乱の淵に陥れようとしていた……。まさにカオス。

ちょっと整理しましょう。まず、Threading.Timerは一番ネイティブなTimerと捉えられます。そのままだと少しつかいづらいので、軽くラップしてイベントベースにしたのがTimers.Timer。Forms.TimerとDispatcherTimerは、それぞれのアプリケーション基盤で時間を計って伝達してくれるというもの、UI系でのInvokeが不要になるので便利。と、それなりに役割の違いはあります。微妙な差ですが。

最後のObservable.TimerはIObservableで通達してくれるのでRxと非常に相性が良いタイマー。また、タイマーを行う場所もISchedulerで任意に指定できるので、ThreadPoolでもDispatcherでもCurrentThread(この場合はSleepで止まるので固まりますけどね)でも、もしくは仮想スケジューラ(任意に時間を動かせるのでテストが簡単になる)でも良いという柔軟さが素敵で、Rx以降のプログラミングではタイマーなんてObseravble.Timer一択だろ常識的に考えて。という勢い。(精度は若干落ちるので、よほど精度を求める時はThreading.Timerを使いましょう)。だと思っていた時もありました。

一時停止出来ないんですよ、Observable.Timer。発動したらしっぱなし。Stopはできる(Disposeする)けど、そうしたら再開は出来ない。それじゃあ困る場合があります!はい。結構あります。そういう場合はTimers.TimerをFromEventでラップする。それはそれで良いのですが、Observable.TimerのISchedulerを指定可能という柔軟さを捨てるのは勿体無いなあ、と思ったのでした。

そこで、今回ReactiveTimerを作りました。機能は、Observable.TimerのStop/Start出来る版です。

[TestClass]
public class ReactiveTimerTest : ReactiveTest
{
    [TestMethod]
    public void TimerTest()
    {
        // テスト用の自由に時間を動かせるスケジューラ
        var testScheduler = new TestScheduler();
        var recorder = testScheduler.CreateObserver<long>();
 
        // 作成時点では動き出さない
        var timer = new ReactiveTimer(TimeSpan.FromSeconds(1), testScheduler);
        timer.Subscribe(recorder); // Subscribeしても動き出さない
 
        timer.Start(TimeSpan.FromSeconds(3)); // ここで開始。初期値を与えるとその時間後にスタート
 
        // 時間を絶対時間10秒のポイントまで進める(AdvanceTo)
        testScheduler.AdvanceTo(TimeSpan.FromSeconds(5).Ticks);
 
        // MessagesにSubscribeに届いた時間と値が記録されているので、Assertする
        recorder.Messages.Is(
            OnNext(TimeSpan.FromSeconds(3).Ticks, 0L),
            OnNext(TimeSpan.FromSeconds(4).Ticks, 1L),
            OnNext(TimeSpan.FromSeconds(5).Ticks, 2L));
 
        timer.Stop(); // timerを止める
        recorder.Messages.Clear(); // 記録をクリア
 
        // 時間を現在時間から5秒だけ進める(AdvanceBy)
        testScheduler.AdvanceBy(TimeSpan.FromSeconds(5).Ticks);
 
        // timerは止まっているので値は届いてないことが確認できる
        recorder.Messages.Count.Is(0);
    }
}

そう、単体テストしたい場合は、TestSchedulerに差し替えれば、AdvancedBy/Toによって、時間を自由に進めることが可能になります。Assertに使っているIs拡張メソッドはChaining Assertionです。Testing周りの詳しい解説はRx-Testingの使い方 - ZOETROPEの日記に書かれています。

CountNotifier/BooleanNotifier

SignalNotifierという名前はよく分からないので、今回よりCountNotifierに変更しました。また、名前空間をNotifiersに変更しました。更に、二値での通知を行うBooleanNotifierを新規追加しました。どちらも、IObservable経由での通知を行うフラグです。

// using Codeplex.Reactive.Notifiers;
 
// 通知可能(IObservable)なboolean flag
var boolFlag = new BooleanNotifier(initialValue: false);
boolFlag.Subscribe(b => Console.WriteLine(b));
 
boolFlag.TurnOn(); // trueにする, trueの状態だったら何もしない
boolFlag.Value = false; // .Valueで変更、既にfalseの状態でも通知する
boolFlag.SwitchValue(); // 値を反転させる
 
// 通知可能(IObservable)なcount flag
var countFlag = new CountNotifier();
countFlag.Subscribe(x => Console.WriteLine(x));
 
countFlag.Increment(); // incしたり
countFlag.Decrement(); // decしたりの状態が通知される
 
// Empty(0になった状態)という判定でフィルタして状態監視したりできる
countFlag.Where(x => x == CountChangedStatus.Empty);

例えば非同期処理を行う際などの、状態の管理に使うことができます。

Pairwise

neue cc - Reactive Extensionsで前後の値を利用するで書いた、前後の値をまとめる拡張メソッドです。

// { Old = 1, New = 2 }
// { Old = 2, New = 3 }
// { Old = 3, New = 4 }
// { Old = 4, New = 5 }
Observable.Range(1, 5)
    .Pairwise()
    .Subscribe(Console.WriteLine);

古い値と新しい値を使って何かしたい場合などにどうぞ。

CatchIgnore

例外処理用に、OnErrorRetryというものを用意していましたが、今回それ以外にCatchIgnoreを追加しました。

// 1, 2
Observable.Range(1, 5)
    .Do(x => { if (x == 3) throw new Exception(); })
    .CatchIgnore()
    .Subscribe(Console.WriteLine);

ようするに、CatchしてEmptyを返す手間を省くためのものです。onErrorにe => {}と書くのと似てますが、シーケンスの途中で捕まえれるので、メソッドチェーンの繋ぎ方によっては全然異なる役割を持つ可能性があります。

その他の削除やバグ修正や見送ったものなど

RxのExperimental版が更新されてたので、それに合わせました。Rxの更新内容はZipとCombineLatestに大量のオーバーロード+配列を受け入れるようになったので、何でも結合できるようになりました。それにともないReactivePropertyでは独自拡張としてCombineLatestのオーバーロードを用意していたのですが、Experimental版のみ削除しました。パフォーマンスもExperimentalのもののほうがずっと良いので、早くStableにも降りてきて欲しいです。

WebRequestのUploadValuesで、値が&で連結されていないという致命的なバグがあったので修正しました。本当にすみません……。また、Silverlightでデザイン画面がプレビューできなくなる不具合を修正しました。デザインモード怖い。

バリデーション周りは、ちょっと大きめに(といっても内部だけの話で外部的には変わらない予定)変更入れようと思ってたのですが、それは次回で。あと、同期系メソッドもバリデーションの成否によって同期するかしないかを決定しようかなあ、とか思うんですが、ちょっと大変なので後になりそう。

まとめ

今回はデータリンクを主眼に置きました。デフォルトモードの変更もその一環です。直接的に意味を見るのなら、厚めのMをスマートにVMとシンクロナイズさせる、ということになります。冒頭の台詞、閉じた世界を開けるための道具です。ObserveProperty(OneWay)、ToReactivePropertyAsSynchronized(TwoWay)、ReactiveProperty.FromObject(OneWayToSource)。

OneWayとかTwoWayとかOneWayToSourceというとおり、VMとMの間のバインディングエンジンだと見ることができます。VとVMの間をWPFなりのフレームワークが担い吸収するように、ReactivePropertyはVMとMの間を吸収します。手書きでバインディングだと、ボイラープレートでは手間だし見通しも悪くなる。このほうが、ずっと、楽だし自然に書けます。

ReactivePropertyはV-VM間の接続も担うため、結果として全てがV-VM-M-VM-Vとして一つに繋がる。何をどう組もうと自然に一つに繋がっていく。わくわくしませんか?むしろカオスの予感がする?けれど、カオスの先に本当の光がある、……かもしれない。

ちなみに同期系のものはみんなプロパティ指定だけでGetとかSetとか自動でやっていますが、動的コード生成(&キャッシュ)によりハイパー高速化されているので、パフォーマンス上の問題はありません。そこは安心してください。というと何か凄そうなことやってる気がしますが、勿論そんなことはなくて、偉大なるExpressionTreeに全面的にお任せしているだけだったり。

Reactive Extensionsで前後の値を利用する

@Toya256tweetさんの作成されたDependency Variable Libを見て、ReactivePropertyでも大体再現できるかなあ、でもOldValueとNewValueのところが少し面倒なのよね、というところで一例。ReactivePropertyの値の変更時に、古い値と新しい値を同時に得られるようにします。

var p = new ReactiveProperty<int>(1, mode: ReactivePropertyMode.RaiseLatestValueOnSubscribe);
 
p.Zip(p.Skip(1), (Old, New) => new { Old, New })
    .Subscribe(a => Console.WriteLine(a.Old + " -> " + a.New));
 
p.Value = 10; // 1 -> 10
p.Value = 100; // 10 -> 100

挙動は@okazukiさんの解説されている通りです。残念ながら、頭やわらかい、というわけではなくて頻出パターンのイディオムなだけなので、ただたんに覚えているから、というだけです、がくり。まあ、LINQにせよRxにせよ、メソッドの組み合わせで成り立っているということは、パターン化しやすいということなのですね。イディオムを知っていればいるほど、更にそのイディオムを組み合わせて、と、手法は無限に広がっていきます。

私は非同期をvoidにしてモデル作り込むっての好きくないです。IObservableなりTaskなりを返してくれれば、先があるのですが、そうでないとやりようがないですから。例えばデータモデル考え中 - 急がば回れ、選ぶなら近道で示される「2」のパターンが、Silverlightなどでの従来のやり方だったと思われます。実行のトリガーだけを外から渡して、モデルの中で結果は閉じる。変更はINotifyPropertyChanged経由で通知。正直言って、私はこのやり方はナシだと思っています。スパゲティになりがちだから。Rxは「3」のパターンに近いと思います。順序の制御は、まさにミドルウェア足るReactive Extensionsが保証する。柔軟性は見ての通りで、無限の広がりがあります。

今まではコールバックしかなかったので必然的に2に収まらざるを得なかったですが、今はRxもあるし、C#5.0からはawaitもあるし、なので、モデルの作り方も「変わっていく」と思います。Viewの機能の強さによってViewModelのありようが変わるように、言語やフレームワークの機能の強さによってModelのありようが変わるのは当然でしょう。

ScanとPairwise

さて、自分自身と結合というのは、結局のところ二つ購読しているということなので、これはIObservableがHotでないと成り立ちません(ReactivePropertyはHotです)。というわけで、ColdなIObservableでも対応したい時はScanを使うといいでしょう。HotとかColdとか何言ってるのか分からないという場合はReactive Extensions再入門 その5「HotとCold」 - かずきのBlog@Hatenaを読むといいでしょう。最近、自分で解説してるのを放棄しだしてる気がするよくない傾向、ではなくて、次回のReactive Extensions(Rx)入門 - @ITではまさにObservable.TimerとフツーのTimerを使ってColdとHotの解説しようと思ってたのですよ!ネタ被った、けれど気にせず書きます:)

var p = new ReactiveProperty<int>(1, mode: ReactivePropertyMode.RaiseLatestValueOnSubscribe);
 
var oldNewPair = p.Scan(Tuple.Create(0, 0), (t, x) => Tuple.Create(t.Item2, x)).Skip(1);
 
oldNewPair.Subscribe(Console.WriteLine);
 
p.Value = 10; // (1, 10)
p.Value = 100; // (10, 100)

Scanは自分自身の前の値を参照できるので、色々と応用が効きます。値の入れ物のための初期値は不要なのでSkip(1)で除去してやるのがポイント。

もう一つ、メソッドの組み合わせでのパターン化、というのは、つまりパーツ化しやすいということでもあります。拡張メソッドに分離してやりましょう。

public static class ObservablePairwiseExtensions
{
    // OldNewPair<T>はReactivePropertyに入っています
    // using Codeplex.Reactive.Extensions;
 
    public static IObservable<OldNewPair<T>> Pairwise<T>(this IObservable<T> source)
    {
        return source.Scan(
                new OldNewPair<T>(default(T), default(T)),
                (pair, newValue) => new OldNewPair<T>(pair.NewItem, newValue))
            .Skip(1);
    }
 
    public static IObservable<TR> Pairwise<T, TR>(this IObservable<T> source, Func<T, T, TR> selector)
    {
        return source.Pairwise().Select(x => selector(x.OldItem, x.NewItem));
    }
}
 
// ↑というような拡張メソッドを作ってやったとして
var p = new ReactiveProperty<int>(1, mode: ReactivePropertyMode.RaiseLatestValueOnSubscribe);
 
p.Pairwise().Subscribe(x => Console.WriteLine(x.OldItem + " -> " + x.NewItem));
 
p.Value = 10; // 1 -> 10
p.Value = 100; // 10 -> 100

OldNewPairを使ったのは、TupleがSL/WP7にないから、というのと、OldItemとNewItemというプロパティ名に意味があって、分かりやすいから、です。基本的にC#でTupleを使うことはあんまないですね。LINQのパイプライン内でならば匿名型、それを超えるなら面倒くさくてもクラスを立ててあげたほうがいいと、私は思っています。勿論、今後Tupleのための構文やパターンマッチが入るとしたら別ですけど。というか、つまるところ専用構文がない状態ではTupleを使うメリットはそんなにないのです。匿名型かわいいよ匿名型。言語比較の際に、C#はTupleがこんな腐ってるぜー、とかやられるのはちょっと勘弁願いたいところ(まぁでも普通に敵いませんのは認めます、けれど言語・IDE・フレームワークは三位一体だとも思っています。引き離して単独で評価することには、あまり価値を感じません。IDEでうまく機能することを優先した言語、それを前提にしたフレームワーク。どの要素も引き離せませんから。はいはい、C#がお好きなんですね、という感じですが、でも例えばHTML/ブラウザというGUIフレームワークの上だったらJavaScriptがベストだ、といった捉え方でもありますね)

それはともかくとして、Pairwiseは多用しそうなので、次のReactiveProperty(ver.0.3)で入れたいと思います(あとOldNewPairのToStringのオーバーライド)。ちなみにlinq.js - LINQ for JavaScriptにはPairwise、入ってます。そう、Rxでの頻出パターンということは、それはIx(Enumerable)にも存在するパターンなのです。この辺がRxの面白いところです!私にとって、こういった書き方の初出は前後の値も利用したシーケンス処理 - NyaRuRuの日記でした。

ObserveChanged

突然出てきたOldNewPairですが、これが既にReactiveProperty内で定義されているのは、ObservableCollectionの拡張メソッド群で使用しているからです。今まで紹介していなかったと思うので、ここで紹介しましょう。

// using Codeplex.Reactive.Extensionsとすると
// ObservableCollection<T>に(ReactiveColelctionとか継承したものでも可)
// ObserveXxxChangedという拡張メソッドが利用できる
var collection = new ObservableCollection<int>();
 
// 追加されたのを監視できる、IObservable<T>
collection.ObserveAddChanged()
    .Subscribe(x => Console.WriteLine("Add:" + x));
// 削除されたのを監視できる、IObservable<T>
collection.ObserveRemoveChanged()
    .Subscribe(x => Console.WriteLine("Remove:" + x));
// 置換を監視できる、IObservable<OldNewPair<T>>
collection.ObserveReplaceChanged()
    .Subscribe(p => Console.WriteLine(p.OldItem + "→" + p.NewItem));
// リセットを監視できる、IObservable<Unit>
collection.ObserveResetChanged()
    .Subscribe(_ => Console.WriteLine("Clear"));
 
collection.Add(100); // Add:100
collection.Add(1000); // Add:1000
collection[1] = 300; // 1000→300
collection.Remove(100); // Remove:100
collection.Clear(); // Clear

この手の監視では、通常CollectionChangedイベント経由でNotifyCollectionChangedEventArgsを使って値を取り出すわけですが、型がobject[]なので一々キャストしたりなど、非常に使いにくいと思っていました。ObserveXxxChangedを使えば、完全にタイプセーフで、値も取り出しやすい形に整形してくれています。是非是非どうぞ。

まとめ

@Toya256tweetさんにも示唆頂いたのですが、ReactivePropertyはMVVMに限定されない、汎用的なものだと考えています。値の導出ルールを宣言的に書く、というのは色々なところで使える、気がします。でもやはり、Functional Reactive Programmingが全然流行ってないことを考えても、ルールによって自動的に変動する値って、基本的にGUI向けなのだろうなあ、って。そして、GUIで強いのはやっぱJavaとか.NETといったFRP不毛地帯なので、流行るなんて考えられないことでした。しかし、今は違う。C#にはRxが来た。C#で実現できるのならば、強力なGUIプラットフォームが目の前にあるわけなので、かなり可能性はあるんじゃないかな!と思いたいところです。

d.y.d. - ReaJ / Reactive JavaScriptの例は

// RaiseLatestValueOnSubscribeはv0.3ではデフォルトに変更する予定
var mode = ReactivePropertyMode.RaiseLatestValueOnSubscribe;
 
var x = new ReactiveProperty<int>(10, mode);
var y = x.Select(n => n + 100).ToReactiveProperty(mode: mode);
x.Value = 20;
x.Value = 30;
Console.WriteLine(y.Value); // 130

まあ、不格好です。ReactiveProperty用の専用構文でも用意してくれないとね、rp x = 10; rp y = x + 100; とかで上記の形に整形されたら素敵なのですが。というのはともかくとして、一応、実現できています。GUI環境への反映はWPFのバインディング機構に投げて解決ですし。JavaScriptにおいても、ReactivePropertyを移植して、ベースとしてKnockout.js辺りを採用すればいい感じに実用的になりそうです。その辺は追々やっていきたいところ。

勿論、Rx自体の可能性はGUI(や非同期)だけに閉じているわけではないので、全く別なところでの可能性、使い道というのも追い求めていきたいです。

ともあれともかく、ReactiveProperty、試してみてくださいな。

Chaining Assertion ver1.6.1.0

Chaining Assertionとは、メソッドチェーンな形で簡単にユニットテストを書けるようにする拡張メソッドです。何でそういうのが必要なのか、とかの理由などはneue cc - テストを簡単にするほんの少しの拡張メソッドで。

最近こっそり小さな更新が続いているのですが、今回の更新は、IsNullにmessageが指定できるようになりました。実のところ、他のIsは指定できたのですが、IsNullだけ指定不可能でした。理由はただたんに忘れてたから、です。とてもしょうもない……。と、@okazukiさんにChainingAssertion使ってみたで指摘頂きました。いやあ、ありがとうございます。

そんなこんなで見直していて、そういえば params object[] parameters なオーバーロードが欠けてるなあ、入れたほうがいいかしらん、と少し実装初めてからやめました。やりたければstring.Format使ってください、はい。ちなみに理由はオーバーロードが必要(messageのほうに{}が入っていてparametersは空、というケースを避けるため、paramsとはいえ別のオーバーロードを用意する必要がある)だからです。

オーバーロードは減らしたいんです。少ないほうが分かりやすいというのは自明な話だと思います。使いやすいAPIのためには、クラスの数を減らそう、メソッドの数を減らそう、オーバーロードの数を減らそう、引数の数を減らそう。少ないことは美です。それでですね、メソッド数を減らすためもあって、Isはかなりオーバーロード嵩んでいるのですよね。だから、瑣末な機能を追加するためだけにホイホイとオーバーロードは足せません。

その他

@shinsukeodaさんにChainingAssertion for MSTest のパラメタライズドテストを NUnit 感覚で利用すると… で紹介頂き本当にありがとうございます。本題の、パラメタライズドテストについてですが、これが非常に悩ましい。実装的にビミョーになってしまう、というのもそうなのですが、NUnitは本当のパラメタライズドテストで、テストケースがバラバラになるのですが、ChainingAssertionのものは擬似的なものに過ぎないので、テスト結果的には一つのテストケースなのですね。そして、一つのケースなのにInitializeやCleanupを呼んでいく、という挙動がアリなのかナシなのかが、自分のなかで答えがでないのです。なので、今はちょっと見送りです。もう少し考えて答えが出たら、その時に、かしらん。

Prev |

Search/Archive

Category

Profile


Yoshifumi Kawai
Microsoft MVP for Visual C#

April 2011
|
March 2013

Twitter:@neuecc