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

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

Code First

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

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

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

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

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

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

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

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

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

実際に使う場合は

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

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

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

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

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

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

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

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

リレーション

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

mcd.SubmitChanges();

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

var mcd = new McDonald();

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

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

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

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

DataContextのDispose

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

.NET版との差分

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

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

学習リソース

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

まとめ

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

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

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

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

基礎からのCode Contracts

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

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

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

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

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

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

Reactive Extensionsで非同期処理を簡単に

すまべん特別編「Windows Phone 7 開発ブーストアップ」@関東にて、Reactive Extensionsの概要と、特に非同期を中心に話しました。以下、発表資料になります。

当初は初心者向け、と思ったんですが、どう見ても一回触ったことのある人向けですねこれ……。Rxを触ったことない人は、よくわからないけど普通に書くと大変なのがスッキリして何だか凄そう、触ってみようと思ってもらえれば。Rxを既に触っている人には使いこなしのTipsとして役立てて貰えればと思います。

はい、Ustreamも残っています。マイクがなかったからという言い訳をしますが、声が全然入ってないですね、声入ってないということは会場でもモゴモゴーという感じで聞き取りにくかったはずで、すみません。それと、「まぁ」言い過ぎ。繋ぐ言葉を捜す枕詞として使いまくりで、うわちゃー、という感じ。慣れてないというか、こういうの(ほぼ)初めてで全然分かってなかったんですが、これを活かして次も頑張りたいです。(Usreamで自分の発表が自分で後から見れるのは自分が嬉しい(笑))。スピーカーできて楽しかったです。機会をくれたすまべんの方々に感謝します。

……こういう機会がないと中々まとめないよね、というのもかなりあったり。合成系のメソッドの図はずーっと書こうと思っていて、書いてなくてこのセッションが初めてです。IObservableは時間軸に乗っているという話もこれが初めて。Twitterでボソッと書くだけじゃなくて、しっかりブログにまとめるようにならないといけないなぁ。

Reactive Extensionsによる非同期クエリ

InfoQ: Future、性能、依存性の低減など多くの改善がされたAkka 1.1リリースから「- Futureは完全にモナドになった。したがってfor内包表記を利用できる。」。リスト内包表記は、ようするところLINQなわけなので、では、とりあえずC# + Reactive Extensionsで。

// 非同期に対するLINQ(map, filter, etc...)
var asyncQuery =
    from a in Observable.Start(() => 10 / 2)
    from b in Observable.Start(() => a + 1)
    from c in Observable.Start(() => a - 1)
    select b * c;

// 非同期のまま実行したいならSubscribe
var canceler = asyncQuery.Subscribe(Console.WriteLine);

// 実行をキャンセルする場合はSubscribe時の戻り値をDispose
canceler.Dispose();

// 同期的に待って値取得したいならFirst
var result = asyncQuery.First();

Rx抜きで、TaskのContinueWithで↑を書くのはカッタルイ。また、Rxでもメソッド構文でSelectManyを連鎖でも辛い。何故か、というと、aの値をcの部分で使えないから。メソッドチェーンの形だと、どうしても一つ手前の値しか持ち越せない。そこで、クエリ構文が活きます。また、LINQであるが故にクエリ構文が使えるRxの嬉しさ。

じゃあ、調子にのってFutures (Scala) — Akka DocumentationをRxで書き換えてみようかしら。同じような内容としては以前にRxを使って非同期プログラミングを簡単にという記事でTaskと比較していたのでそちらも参照を。

// directly
var f1 = Observable.Return("Hello World");

// LINQが使える
var f2 = f1.Select(x => x.Length);

// Subscribeまで実行が遅延されるのでSleepしないよ
var f3 = Observable.Defer(() =>
{
    Thread.Sleep(1000);
    return Observable.Return("Hello World");
});
var f4 = f3.Select(x => x.Length); // まだ実行されないよ
var result = f4.First(); // ここで実行

// Observableの連鎖はSelectManyで
var f5 = f1.SelectMany(x => f3);

// SelectManyはクエリ構文のfrom連打でも置き換えられる
var f6 = from a in Observable.Return(10 / 2)
         from b in Observable.Return(a + 1)
         from c in Observable.Return(a - 1)
         select b * c;

と、この辺まではいいんですが、Composing Futuresが何やってるのかよくわからないので(Scala知識ゼロですみません)、眺めながらAsync CTPでも持ち出します。

async void HomuHomu()
{
    var f1 = Observable.Return(100); // IObservable<int>
    var f2 = TaskEx.FromResult(200); // Task<int>

    var a = await f1; // 何気にIObservable<T>はawait出来る
    var b = await f2; // 当然ですがTask<T>もawait出来る
    var result = a + b; // 300

    // じゃあObservableが幾つも値持ってる場合は?
    var f3 = new[] { "homu", "mado" }.ToObservable();
    var c = await f3;
    Console.WriteLine(c); // "mado"

    // つまり、完了まで待って(OnCompleted)、最後の値が取得される

    // ところでObservable.Return = TaskEx.FromResultなわけですが
    // 以下の3つも同じと捉えていいです
    Task.Factory.StartNew(() => 100);
    TaskEx.Run(() => 200);
    Observable.Start(() => 300); // つまりfunc自体は即時実行
    
    // こちらも等しい(実行が遅延される)
    var t = new Task<int>(() => 100);
    var o = Observable.ToAsync(() => 100);
    // 実行するには
    t.Start();
    o.Invoke(); // もしくは o() ←ただのデリゲートなので

    // ToAsyncでは実行時に引数を渡すことも可能
    var o2 = Observable.ToAsync((int arg) => arg * 2);
    o2(1000).Select(x => x).Subscribe(Console.WriteLine);
}

雰囲気で何となくそうなのだろうと思いつつ、よくわからないので、適当に解釈しながら次。

// IObservable<T>はそのものがリスト状態とも言えるので、
// 複数値を持てるし、LINQなのでSelectしてAggregateも出来る
var futureSum = Observable.Range(1, 1000)
    .Select(x => x * 2)
    .Sum();
    
var sum = futureSum.First();

とりあえずこの辺で(特に言いたいことはない)。全く読めないとこういう時辛い。ActorとReactiveの関係とは、とか、見えそうな見えないような気持ち悪さが脳に渦巻いていて、勉強したいところです。F#で。

上の話とは関係なく告知

二件ほどお話を頂いたので、セッションします。まず、2011/05/21(Sat)にすまべん特別編でRxについて。内容はRx全般になるので、WP7ではなくても適用出来る話になります。

Rxの多くの機能のうち非同期に絞って、かつ、初心者向けに説明しますので、Rxって何それ食べれるのという感じでも全然大丈夫です。また、既に触っている人も、割とためになるTipsが得られるのではないかなと思いながら資料作成中。そうなるよう頑張ります。なので、是非聞きに来てください。セッション資料は、通信環境があればセッション終了後即座に上げるつもりです。なければまた後日で。Ustreamとかもあるのかな?あれば、そちらでも。

もう一件、5月23日(月)にC#ユーザー会でCode Contractsについて。

背景であるDbCなどについてはufcppさんが説明してくださるので、私はCode Contractsとして実装されていることを、Reflectorでこうリライトされるんですねー、とか見ながらデモ中心に、「一から使ってみよう」といった内容にしようと思っています。Code Contracts…… 名前だけなら聞いたことがある、いや、名前も聞いたことない何それ、ぐらいからが対象なのぜ、是非どうぞ。もう使っている、という人には物足りないかも(むしろそこは私が教えて欲しいもがもがもがもが)。

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

Reactive Extensions RC0リリースによる変更点

DevLabsの実験的プロジェクトからData Developer Centerへの正式プロジェクトとして昇格したRxですが、ついにDevLabsのページ自体が消滅(リダイレクトされる)し、いよいよ実験的なノリは一段落し、正式なプロジェクトとしての道を歩み始めたようです。その手始めとして、馬鹿デカい破壊的変更がやってきました。……っていきなりなんじゃそりゃ。

今回の変更はRC0、そしてStableであると銘打たれ、(今度こそ)APIの破壊的変更はないものと思われます。きっとAPIを変更するなら本当の本当に最後のタイミングだから、ということなのでしょうね、変更の嵐は。そんなわけで、DLLの分け方が変わってるし、名前空間も全部変わってるし、メソッド名もばかばか変わってるし、メソッドシグネチャも変わってるし、なくなったのもあるし、大量すぎて書ききれないほどに変更点がある。

しかし、本当の本当に安定版の始まりなので、つまり、学ぶなら今からが正に最適ということです!また、基本的な使い方が変わったわけではないので、既存の知識は生かせますしコード自体もそのままでも大体は行けます。大体は。

詳しい変更内容自体はフォーラム New Release: Reactive Extensions v1.0.10425 に書かれています。こういうのはフォーラムじゃなくてBlogのほうでちゃんと告知してください……。リリースから半月近く経つのに、まだBlogのほうは更新されてないという。

Rxの入手方法・パッケージ・DLL内容について Part2

以前にReactive Extensionsを学習するためのリソースまとめとしてまとめましたが、そのうちDLL内容がまるっと変わったので、そこの部分を修正します。なお、学習リソースについては変りないので、以前のまとめ記事をそのまま参照してください。また、Windows Phone 7に標準搭載されているものは(当然)変わりはありません。最近WP7本体のアップデートにコソッと紛れて更新(バグフィックス)されたようですけど。

Reactive ExtensionsのGet itからDownloadするといいでしょう。色々書いてありますが、2のDownload the Reactive ExtensionsからRx for all Platformsを選べばよいかと思います。

StableとExperimentalがあるように、安定版と実験版に分かれています。そして現時点ではそのページのExperimentalをクリックしてもExperimentalは手に入りません(なにそれ……)。Experimentalが欲しい人はMicrosoft Download Center: Search ResultsのExperimental Releaseから入手するといいでしょう。バージョン番号は、Stableは1.0.10425、Experimentalは1.1.10425になっています。

けれど、何だかんだで更新頻度が高いので、NuGet経由での利用が一番お薦めです。

が、NuGetの画面は新旧入り乱れていて何を入れていいか分からないカオスになっていたりして。説明すると、Rx-Mainとか、名前がRx-になっているものが新しいもので選ぶべきもの。Reactive Extensions -のものは古い残骸なので無視してください。また、NuGet経由で入るものはExperimentalのほうになります。

さて、今回からDLL類の分類がばっさり変わりました。

  • System.Reactive (NuGetではRx-Main)

以前あったCoreExは消滅し、コアコンポーネントはSystem.Reactiveのみとなりました。また、名前空間も全てSystem.Reactive以下に集められていて、例えば今までObservableクラスはSystem.Linqでしたが、今回よりSystem.Reactive.Linqに変更されました(拡張メソッドの利用に名前空間の参照は必須なので、この名前空間のusingは忘れずに)。

  • System.Reactive.Windows.Threading (NuGetではRx-WPF/Rx-Silverlight)

WPF/Silverlight向けでDispatcherに対するObserveOnとSubscribeOnのオーバーロードが追加されています。また、Dispatcher.CurrentDispatcherに対してObserveOn/SubscribeOnを行うObserveOnDispatcher/SubscribeOnDispatcherの利用もこのDLLの参照が必要になりました。それとDispatcherに対してBeginInvokeして実行するスケジューラDispatcherSchedulerが追加されました。

WPF/SilverlightでRxを使う場合はこれの参照は必需と思われます。NuGetを使う場合は依存の解決で、これを選択するとRx-Mainも入れてくれるので、こちらからInstallすると良いでしょう。

  • System.Reactive.Windows.Forms (NuGetではRx-WinForms)

WindowsFormsのControlに対するObserveOnとSubscribeOnのオーバーロードが追加されています。それとControlScheduler(Controlに対してBeginInvokeして実行するスケジューラ)。それだけです。というわけで、その名の通りWinFormsで使う場合だけ、あると便利。

  • System.Reactive.Providers (NuGetではRx-Providers)

Qbservableが収納されています。QbservableはIEnumerableに対するIQueryableみたいなもので、式木からObservableを生成するためのもの。今のとこ有効活用されている例も人もいないと思われます。(というかよく正式リリースにも生き残ったものだ、ぐらいの)。ちなみにQueryable Observableの略だそうで。QBみたいなものだと思えば可愛い!

例えばLinq to Twitterを非同期しかないSilverlightでやるなら、これを使うのが適切でしょう。現状だとコールバックでどうだのという格好悪い仕組みなので。(但し、System.Reactive.Providersは現状ではDesktop版にしか同梱されていませんが)。あとは公式の例として挙げられているWQL Provider。WMIに対するSQLで、クエリ結果はイベント(つまりRx)になる。非常に都合よくQbservableに当てはまるようになってますね。なってるんですが、そう都合よく当てはまるのはこれぐらいしかないのではないか、感もあったり。

いや、むしろ全て非同期なら全部Rxでいいんですよぅー、to SqlだってSqlCommandのBeginExecuteReaderでやればIQueryableじゃなくてIQbservableの出番なんですよぅー。……とはいっても、誰がやるかって話ですね。

  • Microsoft.Reactive.Testing.dll (NuGetではRx-Testing)

ユニットテスト用のモック生成クラス群。使いやすさ的には個人的にはちょっと微妙で、今一つ上手く活用出来なくて色々見送り中。

FromEvent/FromEventPattern

BufferWithCount/BufferWithTimeが統合されてメソッド名がBufferになった、程度の変更は割とどうでもいいのですが、Rxの中核であるFromEventに大きな変更があったのは見逃せません。簡単に解説します。

今までFromEventというメソッド名だったものはFromEventPatternに変わりました。また、戻り値がIEventからEventPatternというものになりましたが、中身はプロパティにSenderとEventArgsを持つという、ほとんど同じものなので、感覚的には一緒です。今までの私の記事や古いウェブ上の記事を見る際にFromEventが使われていたら、それはFromEventPatternに置き換えてください。そうすれば、そのままで動きます。

では新しく新設されたことになるFromEventは何なのかというと、よくわかりません:) そのうち使い方の説明とか出てくると思うのでそれ待ちで……。いやすみません。

ところでInteractiveはどうしたの?ClientProfileは?Asyncは?

死にました。というのもアレですが、とりあえず先行きは不透明です。少なくともStableに同梱されることはないそうです。Experimental側でのリリースは、一応計画はされているようですが、現状は同梱されていません。どうなるんでしょうかねえ……。Interactiveは欲しい人はNuGetに古いのが残っているので、それを使えば、ですね……。

Experimental

さて、今のところ、どのメソッドが実験的とされているのか、見てみましょう。幸いExperimentalAttributeでマークされているので、確認はコードで容易に出来ます。

typeof(System.Reactive.Linq.Observable)
    .GetMethods()
    .Where(mi => mi.GetCustomAttributes(typeof(ExperimentalAttribute), false).Any())
    .Select(mi => mi.Name)
    .Distinct()
    .OrderBy(s => s)
    .ToList() // Interactiveが消滅してしまったから...
    .ForEach(Console.WriteLine);

// 実行結果
Case
Create // 追加のオーバーロードのみ、通常のはExperimentalではない
DoWhile
Expand
For
ForEachAsync
ForkJoin
GetAwaiter
If
IsEmpty
Let
ManySelect
Remotable
Start // 追加のオーバーロードのみ、通常のはExperimentalではない
While

あまり大したものは入ってないようなので、そんな気にすることもないですね。Expandは是非入れてきて欲しいところなのだけど。CreateやStartのオーバーロードはかなりややこしい事になっているので、実験的扱いなのは納得。

ReactiveOAuth ver.0.3.0.0

そんな大移動があったわけでReactiveOAuthも動かなくなってしまった。というわけで更新しました(WP7版の人は気にしなくてもいいです)。

機能は変わってなし。とりあえず最新版のRxで動くように、というだけです。コードが、とにかく名前空間が変わったので全部書き換えて、かなり面倒……。そして一部のメソッドは名前が変わったので、WP7版と完全にコードを共有していたので発狂。ifディレクティブでメソッド呼び出し部分をひたすら書き換え、などというのは見通しも悪いし格好悪いしで最悪なので、別の方法として、WP7側に拡張メソッド作ってコード上の互換を維持するようにしました。

public static class ObservableForCompatible
{
    // 本体のコードはRx RC0に合わせて、WP7側だけ拡張メソッドで同名のものを作って対処
    public static IObservable<IList<T>> Buffer<T>(this IObservable<T> source, int count)
    {
        return source.BufferWithCount(count);
    }
}

他にも挙動が若干変わってるのがあって原因掴むのに泣きそうになったりとか、思った以上に大変だった……。Stableと銘打ってるのに、次にこのクラスの大変更があったらさすがにブチ切れます。今回は、まあ、許す。

Reactive Extensions Extensions(Rxx)

コミュニティから面白いライブラリも上がってきています。

Rx拡張メソッド集。C# 3.0の時も俺々拡張メソッドライブラリがいっぱい出てきましたが、そのノリですね。でも実際、Rx自体は原始的な機能のみなので、非同期処理とかイベント処理にフォーカスする場合は、もう一つ上の層で軽くラップしたライブラリは間違いなく必要だなと思っていますので、こういうのはいいな、と。私自身も非同期処理はReactive Extensions用のWebRequest拡張メソッドとして、結構ガッツし仕込んだものを使い回していて(ReactiveOAuthやUtakotoha の内部はこれ)かなり重宝しています。余裕が出たら、このRxxプロジェクトにJoinしたいな、と思ってます。

Rxの本

オライリーから出ているProgramming C#でお馴染みのJesse Libertyと、共著者としてReactiveUIというRxでWPFのGUI面をサポートするライブラリを作成しているPaul Betts(Microsoft Office Labsに所属)による、Rxの本が今年の秋頃に出る予定です。

執筆陣が豪華だしページ数も現時点amazon表示で300ページと、立派な仕上がりを予感させます。私は予約したよ!

まとめ

今回こそStableなはずなので、ようやーく人に自信を持って薦められるようになりました!いやマヂで。API的にも多分、じゃなくて絶対安定したわけなので、飛び込むなら今!です。

ところで本題とは関係ないんですがスマートフォン勉強会 - すまべん特別編「Windows Phone 7 開発ブーストアップ」@関東でWP7+Rxのセッションをします。入門編として、主に非同期処理にフォーカスして、今すぐコピペで使ってコールバックを撲殺しよう、といった内容を考えていますので、是非聞きに来てください。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2024

Twitter:@neuecc GitHub:neuecc

Archive