« 2012年11月 | メイン | 2013年01月 »

2012年12月 アーカイブ

2012年12月01日

[C#][ラムダ式][匿名メソッド] ラムダ式は常に匿名メソッドよりシンプルに書ける?

Expression

匿名メソッドとラムダ式の違い」と云う記事で、匿名メソッドとラムダ式の意味の違いについて考えた。

それについて、些細な補足をする。

上の記事中で、「ラムダ式を使った方が、型推論の恩恵を存分に受けられ、書き方がぐっとシンプルになる。」、「一般的には、ラムダ式を使うことで書き方もシンプルになることだし、匿名メソッドは使わずにラムダ式で書くようにした方が良いだろう。」と述べた。

一般的にはこれは真実だ。しかし、ごく稀にだが、ラムダ式よりも匿名メソッドの方がシンプルになることがある。

■ ラムダ式が匿名メソッドよりシンプルな書き方になる例

上の記事で述べたように、通常は、ラムダ式の方が型推論が強力なので、シンプルに書ける。

かなり極端な例だが、

using System;

class Subject
{
    public event Action<Subject, object, object, object, object, object> Update;
}

class Program
{
    static void DoSomething(Subject subject)
    {}

    static void Main()
    {
        var subject = new Subject();

        // 匿名メソッドで書いた場合
        subject.Update += delegate(Subject sender, object a, object b, object c, object d, object e) { DoSomething(sender); };

        // ラムダ式で書いた場合
        subject.Update += (sender, a, b, c, d, e) => DoSomething(sender);
    }
}

ラムダ式の方では、型名の記述その他が省略できてシンプルな記述になっている。

■ ラムダ式より匿名メソッドの方がよりシンプルな書き方になる例

ところが、内部で引数を使わないような下の例ではどうだろう。

using System;

class Subject
{
    public event Action<Subject, object, object, object, object, object> Update;
}

class Program
{
    static void DoSomething()
    {}

    static void Main()
    {
        var subject = new Subject();

        // 匿名メソッドで書いた場合
        subject.Update += delegate { DoSomething(); };

        // ラムダ式で書いた場合
        subject.Update += (_, a, b, c, d, e) => DoSomething();
    }
}

匿名メソッドの方が、ちょっとだけシンプルになっている気がしないだろうか。

■ 本日のまとめ

とは云え、これは上で述べているように極端で、そして余り意味のない例だ。

こんなに引数が多いデリゲートなど殆ど必要ないだろう。

矢張り、「一般的には、ラムダ式を使うことで書き方もシンプルになることだし、匿名メソッドは使わずにラムダ式で書くようにした方が良いだろう。」と云って問題ないと思う。

2012年12月02日

[Windows 8] Windows ストア アプリ「Fresh Paint」の紹介

Windows 8 と云えば、タブレットに特化した新しい UI のデザイン (マイクロソフト デザイン) が特徴だ。

今回は、その新しいデザインをタブレットで楽しむのに最適な Windows Store アプリをご紹介しよう。

「Fresh Paint」だ。

Fresh Paint は、素晴らしい UX (ユーザーエクスペリエンス) を持ったペイントアプリだ。

Windows 8 のスタート画面にあるウィンドウズ ストアで "Fresh Paint" で検索すると見つかる。

ウィンドウズ ストアで Fresh Paint の検索
ウィンドウズ ストアで Fresh Paint の検索

次のリンクからも行くことができる。

ウィンドウズ ストアで見つかった Fresh Paint
ウィンドウズ ストアで見つかった Fresh Paint

Fresh Paint は、何と無料だ。

対象年齢は三歳以上となっている。
幼児から大人まで十分にお絵かきが楽しめるだろう。

使ってみよう

Windows ストアからのインストールはとても簡単だ。

インストールが終わったら、スタート画面にタイルが出来ている筈だ。
早速起動してみよう。

Fresh Paint の起動画面
Fresh Paint の起動画面

起動するとこんな感じだ。

Fresh Paint が立ち上がったところ
Fresh Paint が立ち上がったところ

タブレットなら画面上下からのスワイプ、マウスなら右クリック、キーボードからなら Windows + Z で、パレットやアプリバーを開閉できる。

これが何とも心地よい。

Fresh Paint - パレットやアプリバーを閉じた白紙全体が見えている状態
Fresh Paint - パレットやアプリバーを閉じた白紙全体が見えている状態
Fresh Paint - パレットやアプリバーを出したところ
Fresh Paint - パレットやアプリバーを出したところ
Fresh Paint - パレットやアプリバーを出したところ
Fresh Paint - パレットやアプリバーを出したところ

パレットはサーフェスと云うものに切り替えることが出来る。
ここでは紙を選んだり出来るのだ。

Fresh Paint - パレットからサーフェスに切り替えたところ
Fresh Paint - パレットからサーフェスに切り替えたところ

カメラで撮影したり、読み込んだりした写真を下地にすることも出来る。 写真に落書きするのにも使えるのだ。

Fresh Paint - カメラで撮影したり読み込んだりした写真
Fresh Paint - カメラで撮影したり読み込んだりした写真

タブレットで描いてみる

マウスでもそこそこ描けるが、矢張りこの UI が生きるのはタッチによる入力だろう。

SONY VAIO Duo 11で試してみることにした。

SONY VAIO Duo 11 は、Windows 8 搭載で、タブレット スタイルとノートPC スタイルを簡単に切り替えることができるのだ。

Fresh Paint - タブレットで起動したところ
Fresh Paint - タブレットで起動したところ
Fresh Paint - タブレットで指で描画
Fresh Paint - タブレットで指で描画

矢張りとても自然に使える。

とても自然な感覚で、マウスを使っているときのような間接的な感じがない。

ストレスなく描ける。

子供の頃、直接指に絵具を付けて画用紙に書いたことがあるが、あの感覚だ。

いや。あの感覚以上だ。指先が筆になったようだ。

良いスタイラス ペンが欲しいところだ。
きっと、指とはまた違った素敵な描き心地になることだろう。

パレットが秀逸

兎に角パレットの使い心地が堪らない。

出て来るときの感じ。
絵具をパレットに出す感じ。
色を混ぜる感じ。
水を加えて更に混ぜる感じ。

Windows 8 ならではの、描いてるときはフル画面、必要なときに上から指で引っ張り出す、と云う UI にとてもマッチしている。

文章と写真だけでは伝わりにくいだろう。
動画を撮ってみた。


この動画へのリンク
この動画へのリンク

いかがだろうか。

Windows 8 ならではのユーザー エクスペリエンス

Windows 8 と云えば、タブレットに特化した新しい UI のデザイン (マイクロソフト デザイン) が特徴だ。

タッチを使って、これまでの Windows にない、新しい体験がしたいならお勧めのアプリだ。

まるで本物のパレットとイーゼルを使っているような体験が出来るだろう。

特に Windows 8 ならではパレットの使い心地は、是非味わってもらいたい。

紙と筆のよう

こうしたアプリを使っていると、ユーザー体験の大切さを改めて感じる。

紙と筆のようであること。

ユーザーにとって、自然な体験であること。

子供の頃から慣れ親しんだ紙と筆のように使えること。

そして、紙と筆のようでないこと。

紙と筆にはない新しい体験があること。

私は Windows ストア アプリの開発者でもあるのだが、Fresh Paint を使っていて開発者にとって大事なことを学んだ気がした。

[Windows 8] Windows ストア アプリ「Note Anytime」の紹介

Windows ストア アプリ「Fresh Paint」の紹介」と云う記事で Windows 8 ならではのユーザーエクスペリエンスの Fresh Paint を紹介した。

今回は、同じく素晴らしい UX のノート アプリ「Note Anytime」をご紹介する。

Note Anytime

今回は、Windows 8 や Windows RT タブレットでノートを取るのに最適な Windows Store アプリをご紹介しよう。

Windows 8 のスタート画面にあるウィンドウズ ストアで "Note Anytime" で検索すると見つかる。

ウィンドウズ ストアで Note Anytime の検索
ウィンドウズ ストアで Note Anytime の検索

次のリンクからも行くことができる。

ウィンドウズ ストアで見つかった Note Anytime
ウィンドウズ ストアで見つかった Note Anytime

説明のところには次のように書いてある。

★★★Note Anytimeは、MetaMoJiの"こだわり"をもって作られた世界最高のノートアプリです。しかも無料。★★★

これは嘘ではない。

是非次の紹介ビデオを観て欲しい。このノート アプリの凄さが判るだろう。

[Note Anytime] イントロダクション (for Windows)

Note Anytime を使ってみよう

Windows ストア アプリなので、インストールはとても簡単だ。

インストールが終わったら、スタート画面にタイルが出来ているので、ここから起動しよう。

Note Anytime の起動画面
Note Anytime の起動画面
Note Anytime の起動直後
Note Anytime の起動直後

起動するとこんな感じだ。

Note Anytime のサンプルを開いたところ
Note Anytime のサンプルを開いたところ

このアプリには、良いスタイラス ペンを使うべきだろう。

Note Anytime のツール パレットとメニュー
Note Anytime のツール パレットとメニュー

丸いツール パレットが特徴的だ。
このパレットでペンを選んだり、文字入力に切り替えたり、様々なことを書いている近くで行えるようになっている。

マウスよりも、指やスタイラス ペンを意識した UI だ。

特にスタイラス ペンでの操作に最適だろう。

Note Anytime のペンの設定画面
Note Anytime のペンの設定画面
Note Anytime のライブラリ
Note Anytime のライブラリ

ライブラリでは様々なアイテムを選んで使うことができる。

自分で Note Anytime で描いた絵や書いた字をアイテムとしてライブラリに登録することもできる。

Note Anytime のオブジェクト
Note Anytime のオブジェクト

ノートの描画要素はオブジェクトとして配置されている。

つまり、個個の描画要素は後で消したり、移動したり、変形したり、回転したりできる。

Windows 8 ならではのユーザー エクスペリエンス

Fresh Paint もそうだが、Note Anytime Windows 8 に特徴的な新しい UX を存分に楽しめるアプリだ。

是非体験してみて欲しい。

[Windows 8] Windows 8 の使い方を学びたい人のためのリンク集

Windows 8

今年の10月26日に Windows 8 が出てから暫く経った。
使い始めている方も多いだろう。

中には、新しいユーザー インタフェイスの使い方にまだ馴染めずにいる方もいるかも知れない。

そこで、今回は、Windows 8 の使い方などを知ることができる記事を紹介したい。

以下の記事をざっと参考にすれば、Windows 8 を使いこなせるようになるだろう。

Windows 8 の使い方を学びたい人のためのリンク集

先ず、Windows 8 の使い方全般を知りたい人はこちら。

  • Windows 8 レボリューション - @IT
    ここでは以下のことを知ることが出来る。
    • 新しいユーザー・インターフェイスの紹介
    • ユーザー・アカウントの設定方法
    • Surface の紹介
    • Windowsストア・アプリのインストール方法
    • タッチ、マウス、キーボードによる操作の方法

Windows 8 と Windows RT では、タッチ操作が特徴的だ。
その一覧はここで。

Windows 8 と Windows RT では、キーボード ショートカットでチャームを開いたり、アプリバーを開いたり、様様なことが出来る。
その一覧はここで。

Windows 8 で標準でサポートされていない DVD 再生をするための Windows 8 Media Center Pack を 1月31日まで無料で入れることが出来る。
Windows 8 Pro 向けだ。
こちらで。

Windows 8 の新しい UX を体験するための新 PC を買いたい人向け記事はこちら。

Windows 8 に関する最新記事を読みたい方はこちら。

Windows 8 の使い方を動画で学びたい方はこちら。

  • Windows 8 使い方 動画マニュアル

    Windows 8の使い方を解説した動画が多く用意されている。
    幾つか紹介してみよう。
    • Windows 8 使い方  シャットダウン・再起動・スリープ
      (1分10秒)
    • Windows 8 スタート画面の色やデザインを変更する方法
      (1分23秒)
    • スタート画面にアプリを追加する方法 (ピン留め) Windows 8 使い方
      (55秒)
    • Windows8 2つのアプリを並べて表示させる方法
      (1分13秒)
    • Microsoftアカウントを作成してログインアカウントに設定 Windwows8 使い方
      (2分31秒)
    • Windows 8 でコントロールパネルを開く方法
      (1分32秒)

以上を参考に、是非新しくなった Windows をより便利に体験して頂きたい。

2012年12月03日

[C#][ラムダ式][式木] Expression を使ってラムダ式のメンバー名を取得する

Expression

前回「Expression の構造を調べてみる」と云う記事で、Expression の内部のツリー構造を調べた。

その中で、ラムダ式を Expression として扱うことで、式の中の名前が取れることが判った。

今回は これを利用してラムダ式のメンバー名を取得する例を挙げてみたい。

■ ラムダ式のメンバー名を取得する例

先ずシンプルなクラスを一つ用意する。

class Item
{
    public int Id { get; set; }
}

そして、item => item.Id というラムダ式を渡してプロパティ名を取得するメソッドを作ってみよう。

前回の知識が役に立つ。

前回の調査で、ラムダ式の => の右側の式は Body で取れることが解かっている。

次に、Visual Studio のデバッガーなどを駆使して、Body の式の種類を調べよう。Body は MemberExpression であることが判る筈だ。

MemberExpression であれば、プロパティの内の Member の Name で名前が取得できる。

実際にメソッドを作ってみるとこんな感じだ:

using System;
using System.Linq.Expressions;

public static class ObjectExtensions
{
    // Expression からメンバー名を取得
    public static string GetMemberName<ObjectType, MemberType>(this ObjectType @this, Expression<Func<ObjectType, MemberType>> expression)
    {
        return ((MemberExpression)expression.Body).Member.Name;
    }
}

早速使ってみよう。

using System;

class Item
{
    public int Id { get; set; }
}

class Program
{
    static void Test()
    {
        var memberName = new Item().GetMemberName(item => item.Id);
        Console.WriteLine(memberName);
    }

    public static void Main()
    {
        Test();
    }
}

実行結果は下の通りで、ラムダ式からプロパティ名が取得できた。

Id

次に、Item クラスの内部からもやってみよう。

今度のラムダ式は () => Id だ。

ObjectExtensions クラスに先程作ったメソッドから第一引数を無くしたメソッドを追加して、

using System;
using System.Linq.Expressions;

public static class ObjectExtensions
{
    // Expression からメンバー名を取得
    public static string GetMemberName<ObjectType, MemberType>(this ObjectType @this, Expression<Func<ObjectType, MemberType>> expression)
    {
        return ((MemberExpression)expression.Body).Member.Name;
    }

    // Expression からメンバー名を取得
    public static string GetMemberName<MemberType>(Expression<Func<MemberType>> expression)
    {
        return ((MemberExpression)expression.Body).Member.Name;
    }
}

Item クラス内部に Test 用のメソッドを追加し、そこから呼んでみる。

using System;

class Item
{
    public int Id { get; set; }

    public void Test()
    {
        var memberName = ObjectExtensions.GetMemberName(() => Id);
        Console.WriteLine(memberName);
    }
}

class Program
{
    static void Test()
    {
        var memberName = new Item().GetMemberName(item => item.Id);
        Console.WriteLine(memberName);
    }

    public static void Main()
    {
        Test();
        new Item().Test();
    }
}

実行結果は下の通り。クラス内部からもラムダ式からプロパティ名が取得できた。

Id
Id

■ 今回のまとめ

Expression を利用してラムダ式のメンバー名を取得してみた。

しかし、こんなことが何の役に立つのだろうか?

次回から、これを利用した応用例について書こうと思う。

2012年12月04日

各種クラウド ドライブの比較

cloud

最近は、様様なクラウド ドライブを利用している。

PC や各種モバイル デバイスで、共有のドライブが使えるのはとても便利だ。

Web で使えるだけでなく、PC や モバイル デバイスごとに専用のアプリケーションが用意してあるものが多く、それらは、ローカルのドライブと同じように使うことができる。

利用しているものが多くなってきたので、纏めてみた。

名称 サイト 無料スペース (*1) 会社 Web PC (*2) モバイル (*2)
Sky Drive Sky Drive http://windowslive.jp.msn.com/skydrive.htm 7GB Microsoft https://skydrive.live.com Windows, MacWindows Phone,iOS
Google Drive Google Drive http://www.google.com/intl/ja/drive/start/ 5GB Google https://drive.google.com Windows, Mac, Linux iOS, Android, BlackBerry
Dropbox Dropbox http://www.dropbox.com 2GB Dropbox https://www.dropbox.com Windows, Mac,Chrome OS iOS, Android, BlackBerry
SugarSync SugarSync http://www.sugarsync.jp 5GB SugarSync https://[アカウント名].sugarsync.com Windows, Mac,Chrome OS iOS, Android
iCould iCould http://www.apple.com/icloud/ 5GB Apple https://www.icloud.com Windows, MaciOS
Amazon Cloud Drive Amazon Cloud Drive http://www.amazon.co.jp/gp/feature.html?ie=UTF8&docId=3077664656 5GB Amazon https://www.amazon.co.jp/clouddrive Windows, Mac Android
Yahoo! ボックス Yahoo! ボックス http://box.yahoo.co.jp 5GB
(Yahoo! BB会員 50GB)
Yahoo Japan https://box.yahoo.co.jp WindowsiOS, Android
N ドライブ N ドライブ http://ndrive.naver.jp 30GB NHN Japan http://ndrive.naver.jp Windows, MaciOS, Android
KDrive KDrive http://www.kdrive.jp 50GB KINGSOFT https://www.kdrive.jp/index.php?ac=fileview Windows, MaciOS, Android

(*1) 条件を満たすことで、無料の儘容量を増やせるものもある。
(*2) 但し、純正アプリ以外で使えるものはある。

私は、Sky Drive を中心に、Dropbox と Google Drive を併用している。

「複数のデバイスで同じストレージにアクセスできる」のはとても便利で、今やなくてはならない。

いずれは、ドライブすら意識させないクラウド環境が当たり前になるのかも知れないが、過渡期として「丁度いい」便利さだ。

2012年12月05日

[C#][dynamic] DynamicObject を使ってみよう

Dynamic

C# 4 から dynamic が使えるようになった。

動的言語のように、動的にプロパティを参照したり、メソッドを呼んだり出来るようになった訳だ。

そして、.NET Framework では 4 から System.Dynamic と云う名前空間ができた。

今回は、この名前空間の中の DynamicObject を使ってみたい。

■ DynamicObject を使った例

・DynamicObject

DynamicObject は、メソッド呼び出し、プロパティへのアクセス、インデックスによるアクセス等のそれぞれに対応する virtual メソッドを持つ。

例.

virtual メソッド名 説明
TrySetMember オーバーライドして、プロパティに値が設定されるときの動作を定義できる
TryGetMember オーバーライドして、プロパティから値が取得されるときの動作を定義できる
TrySetIndex オーバーライドして、インデックスを用いて値が設定されるときの動作を定義できる
TryGetIndex オーバーライドして、インデックスを用いて値が取得されるときの動作を定義できる
TryInvokeMember オーバーライドして、メソッドが呼び出されるときの動作を定義できる

DynamicObject から派生したクラスを作り、これらの virtual メソッド をオーバーライドすることで、動的な動作を定義することができる。

・DynamicObjectを使った例 1

実際に試してみよう。

今回は、TrySetMember と TryGetMember をオーバーライドしてプロパティにアクセスしてみる。

using System;
using System.Dynamic;

class Dynamic : DynamicObject
{
    // プロパティに値を設定しようとしたときに呼ばれる
    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        return base.TrySetMember(binder, value);
    }

    // プロパティから値を取得しようとしたときに呼ばれる
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        return base.TryGetMember(binder, out result);
    }
}

class Program
{
    static void Main()
    {
        dynamic item = new Dynamic();
        item.Id = 100; // 実行時エラー (Id の定義がない)
        Console.WriteLine(item.Id); // 実行時エラー (Id の定義がない)
        item.Name = "田中一郎"; // 実行時エラー (Name の定義がない)
        Console.WriteLine(item.Name); // 実行時エラー (Name の定義がない)
    }
}

Visual Studio を使って、オーバーライドした TrySetMember と TryGetMember にブレークポイントを置いてデバッグ実行してみると、それぞれプロパティの設定時と取得時にちゃんと呼ばれていることが判る。

DynamicObjectを使った例 1 - ブレークポイントで止めたところ
DynamicObjectを使った例 1 - ブレークポイントで止めたところ

base.TrySetMember(binder, value) と base.TryGetMember(binder, out result) は false を返している。

TrySetMember が false を返すとプロパティの設定に失敗し、TryGetMember が false を返すとプロパティの取得に失敗する。

従って、このプログラムを実行してみると、以下のような実行時エラーになる。

DynamicObjectを使った例 1 - 実行時エラー
DynamicObjectを使った例 1 - 実行時エラー
・DynamicObjectを使った例 2

TrySetMember と TryGetMember の中をもう少しちゃんと実装して、動くようにしてみよう。

TrySetMember の中で、Dictionary を使って設定した値を覚えるようにしてみる。

そして、同じプロパティが再設定されるときは、型が同じか派生クラスのときだけ許すようにしてみる
(この実装はやや妥当ではないが、今回はこうしてみる)。

そして、TryGetMember の中では、Dictionary から値を取り出して返すようにしてみよう。

こんな感じだ。

using System;
using System.Collections.Generic;
using System.Dynamic;

class Dynamic : DynamicObject
{
    // observableTarget の該当するプロパティ名毎に型と値を格納
    Dictionary<string, Tuple<Type, object>> values = new Dictionary<string, Tuple<Type, object>>();

    // プロパティに値を設定しようとしたときに呼ばれる
    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        Tuple<Type, object> tuple;
        // 該当するプロパティの型と値を values から取得
        if (values.TryGetValue(binder.Name, out tuple)) {
            var valueType = value.GetType();
            // もしプロパティに設定しようとしている値の型が、そのプロパティの型もしくはそのサブクラスでなかったら
            if (!valueType.Equals(tuple.Item1) && !valueType.IsSubclassOf(tuple.Item1))
                return false;
            // 元の型の儘で値を再設定
            values[binder.Name] = new Tuple<Type, object>(tuple.Item1, value);
            return true;
        }
        // 型と値を新規に格納
        values[binder.Name] = new Tuple<Type, object>(value.GetType(), value);
        return true;
    }

    // プロパティから値を取得しようとしたときに呼ばれる
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        Tuple<Type, object> tuple;
        // 該当するプロパティの型と値を values から取得
        if (values.TryGetValue(binder.Name, out tuple)) {
            result = tuple.Item2;
            return true;
        }
        result = null;
        return false;
    }
}

class Program
{
    static void Main()
    {
        dynamic item = new Dynamic();
        item.Id = 100; // OK
        Console.WriteLine(item.Id); // OK
        //item.Id = "田中一郎"; // 実行時エラー (異なった型の値を設定しようとしている)
        item.Name = "田中一郎"; // OK
        Console.WriteLine(item.Name); // OK
        //Console.WriteLine(item.Address); // 実行時エラー (設定していないプロパティの値を取得しようとしている)
    }
}

今度の実行結果は次の通り。

100
田中一郎

item.Id に異なった型の値を設定しようとしたり、設定していないプロパティの値を取得しようとしたりしている箇所は、実行時エラーになる。
それ以外は正常に動く。

プロパティの再設定時に、再設定する値の型が派生クラスなら設定できるが派生クラスでなければ設定できないことも、確認しておこう。

class Super {}

class Sub : Super {}

class Program
{
    static void Main()
    {
        dynamic item = new Dynamic();

        item.Property1 = new Super(); // OK
        item.Property1 = new Sub(); // OK

        item.Property2 = new Sub(); // OK
        item.Property2 = new Super(); // 実行時エラー (型が違うし、派生クラスでもない)
    }
}

最後の、型が同じでも派生クラスでもなかったときだけ実行時エラーとなる。

■ ExpandoObject を使った例

次に、同じ名前空間 System.Dynamic の中の ExpandoObject も試してみよう。

DynamicObject は派生して使うが、ExpandoObject は派生せずにその儘使う。

こんな感じだ。

using System;
using System.Dynamic;

class Program
{
    static void Main()
    {
        dynamic item = new ExpandoObject();

        item.Id = 100; // OK
        Console.WriteLine(item.Id); // OK
        item.Id = "田中一郎"; // OK
        Console.WriteLine(item.Id); // OK
        item.Name = "田中一郎"; // OK
        Console.WriteLine(item.Name); // OK
        //Console.WriteLine(item.Address); // 実行時エラー(設定していないプロパティの値を取得しようとしている)
    }
}

実行結果は次の通り。

100
田中一郎
田中一郎

ExpandoObject はその儘で、動的にプロパティを設定したり、取得したりできる。

こちらは、最後の設定してないプロパティを取得しようとしたとき以外は、実行時エラーにならない。

■ 今回のまとめ

今回は、DynamicObject を使ってみた。

近く、これの応用例として、DynamicObject をオブザーバー パターンの実装に利用してみる予定だ。

お楽しみに。

2012年12月06日

[C#][dynamic] DynamicObject を使ってみよう その 2

Dynamic

前回の「DynamicObject を使ってみよう」の続き。

前回は、DynamicObject と ExpandoObject を使ってみた。

DynamicObject の派生クラスや ExpandoObject は連想配列のように機能した。
但し、通常の連想配列とはインタフェイスが異なる。

今回も、引き続き DynamicObject と ExpandoObject を使ってみよう。

■ DynamicObject を使った例

DynamicObject の派生クラスがインタフェイスの異なる連想配列のように使える、と云うことを利用してちょっとしたラッパー クラスを作ってみよう。

例えば、ASP.NET や ASP.NET MVC では、セッション変数を利用する際、以下のように連想配列のように使う。

    Session["Id"] = 100;
    int? id = Session["Id"] as int?;

DynamicObject を利用したラッパー クラスを作ってみよう。

例えば、こんな感じだ。

using System.Dynamic;
using System.Web;

public class SessionObject : DynamicObject
{
    readonly HttpSessionStateBase session;

    public SessionObject(HttpSessionStateBase session)
    {
        this.session = session;
    }

    // プロパティに値を設定しようとしたときに呼ばれる
    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        session[binder.Name] = value;
        return true;
    }

    // プロパティから値を取得しようとしたときに呼ばれる
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        result = session[binder.Name];
        return true;
    }
}

すると、従来のこんな書き方が、

// ASP.NET MVC の場合の一例 (従来の書き方)
using System.Web;
using System.Web.Mvc;
using System.Web.UI;

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var id = Session["Id"]; // まだ Session["Id"] は無いので、id は null
        Session["Id"] = 100; // Session["Id"] に 100 を設定
        id = Session["Id"]; // id には 100 が返ってくる

        var item = Session["Item"]; // まだ Session["Item"] は無いので、item は null
        Session["Item"] = new { Id = 200, Name = "田中一郎" }; // Session["Item"] に匿名型のオブジェクトを設定
        id = DataBinder.Eval(Session["Item"], "Id"); // id には 200 が返ってくる

        return View();
    }
}

SessionObject クラスを使うことによって、こんな書き方になる。

// ASP.NET MVC の場合の一例 (SessionObject クラスを使った場合)
using System.Web;
using System.Web.Mvc;

public class HomeController : Controller
{
    dynamic session = null;

    public ActionResult Index()
    {
        session = new SessionObject(Session);

        var id = session.Id; // まだ session.Id は無いので、id は null
        session.Id = 100; // session.Id に 100 を設定
        id = session.Id; // id には 100 が返ってくる

        var item = session.Item; // まだ session.Item は無いので、item は null
        session.Item = new { Id = 200 }; // session.Item に匿名型のオブジェクトを設定
        id = session.Item.Id; // id には 200 が返ってくる

        return View();
    }
}

dynamic に書けることで、コードがややシンプルになった。

一応、Visual Studio でデバッグ実行し、デバッガーを使って値をチェックしてみると、次のようになった。

DynamicObjectを使った例 - デバッガーでの値のチェック - 「まだ session.Id は無いので、id は null」
1. 「まだ session.Id は無いので、id は null」
DynamicObjectを使った例 - デバッガーでの値のチェック - 「id には 100 が返ってくる」
2. 「id には 100 が返ってくる」
DynamicObjectを使った例 - デバッガーでの値のチェック - 「まだ session.Item は無いので、item は null」
3. 「まだ session.Item は無いので、item は null」
DynamicObjectを使った例 - デバッガーでの値のチェック - 「id には 200 が返ってくる」
4. 「id には 200 が返ってくる」

まあ、実用的な意味合いは余りないが、連想配列的な部分には、使える可能性がある、と云う一例だ。

■ ExpandoObject を使った例

次は、ExpandoObject だ。

・ExpandoObject にプロパティっぽいものを追加

前回は、ExpandoObject を使って動的なプロパティっぽいものを実現した。

再度やってみよう。

using System;
using System.Dynamic;

class Program
{
    static void Main()
    {
        dynamic item = new ExpandoObject();

        // プロパティっぽいものを追加 その1
        item.Id = 10;
        Console.WriteLine(item.Id);
    }
}

実行結果は次の通り。

10

もうちょっと複雑な例。

using System;
using System.Dynamic;

class Program
{
    static void Main()
    {
        dynamic item = new ExpandoObject();

        // プロパティっぽいものを追加 その2
        item.SubItem = new { Id = 100, Name = "田中一郎" };
        Console.WriteLine(item.SubItem);
        Console.WriteLine(item.SubItem.Id);
        Console.WriteLine(item.SubItem.Name);
    }
}

実行結果は次の通り。

{ Id = 100, Name = 田中一郎 }
100
田中一郎
・ExpandoObject に static メソッドっぽいものを追加

オブジェクトが何でも追加できるのであれば、デリゲートだって追加できる筈だ。

デリゲートが追加できるのであれば、メソッドっぽいものも実現できるのではないか。

試しに、static メソッドっぽいものの追加を試してみよう。

こんな感じ。

using System;
using System.Dynamic;

class Program
{
    static void Main()
    {
        dynamic item = new ExpandoObject();

        // static メソッドっぽいものを追加
        item.SetId = new Action<dynamic, int>((o, value) => { o.Id = value; });
        item.GetId = new Func<dynamic, int>(o => o.Id);

        // 呼んでみる
        item.SetId(item, 30);
        Console.WriteLine(item.GetId(item));
    }
}

実行結果は次の通り。

30
・ExpandoObject に イベントっぽいものを追加

デリゲートが追加できるのであれば、もうちょっとイベントっぽくしてみよう。

こんな感じだ。

using System;
using System.Dynamic;

class Program
{
    static void Main()
    {
        dynamic item = new ExpandoObject();

        // イベントっぽいものを追加

        // 1. 先ず、Update と云う名のイベントっぽいものを用意
        item.Update = null;

        // 2. 次に、Update にイベントハンドラーっぽいものを追加
        item.Update += new Action<dynamic, EventArgs>((sender, _) => Console.WriteLine(sender.Name));

        // 3. そして、SetName と云うメソッドっぽいものを用意して、中で Update と云うイベントっぽいものを起こす
        item.SetName = new Action<dynamic, string>(
            (o, value) => {
                o.Name = value;
                if (o.Update != null)
                    o.Update(o, EventArgs.Empty);
            }
        );

        // 4. では、試してみよう:
        // SetName を呼ぶと Update が起きて Console.WriteLine(sender.Name) されるかな?
        item.SetName(item, "田中次郎");
    }
}

実行結果は次の通り。

田中次郎

イベントっぽい。

・ExpandoObject のメンバーの一覧

ここまでを纏めてみる。最後にメンバーの一覧を取ってみよう。

using System;
using System.Dynamic;

class Program
{
    static void Main()
    {
        dynamic item = new ExpandoObject();

        // プロパティっぽいものを追加 その1
        item.Id = 10;
        Console.WriteLine(item.Id);

        // プロパティっぽいものを追加 その2
        item.SubItem = new { Id = 20, Name = "田中一郎" };
        Console.WriteLine(item.SubItem);
        Console.WriteLine(item.SubItem.Id);
        Console.WriteLine(item.SubItem.Name);

        // static メソッドっぽいものを追加
        item.SetId = new Action<dynamic, int>((o, value) => { o.Id = value; });
        item.GetId = new Func<dynamic, int>(o => o.Id);

        // 呼んでみる
        item.SetId(item, 30);
        Console.WriteLine(item.GetId(item));

        // イベントっぽいものを追加

        // 1. 先ず、Update と云う名のイベントっぽいものを用意
        item.Update = null;

        // 2. 次に、Update にイベントハンドラーっぽいものを追加
        item.Update += new Action<dynamic, EventArgs>((sender, _) => Console.WriteLine(sender.Name));

        // 3. そして、SetName と云うメソッドっぽいものを用意して、中で Update と云うイベントっぽいものを起こす
        item.SetName = new Action<dynamic, string>(
            (o, value) => {
                o.Name = value;
                if (o.Update != null)
                    o.Update(o, EventArgs.Empty);
            }
        );

        // 4. では、試してみよう:
        // SetName を呼ぶと Update が起きて、イベントハンドラーの中で Console.WriteLine(sender.Name) されるかな?
        item.SetName(item, "田中次郎");

        // メンバーの一覧の取得
        Console.WriteLine("\nメンバーの一覧:\n");
        foreach (var member in item)
            Console.WriteLine(member);
    }
}

実行結果は次のようになった。

10
{ Id = 20, Name = 田中一郎 }
20
田中一郎
30
田中次郎

メンバーの一覧:

[Id, 30]
[SubItem, { Id = 20, Name = 田中一郎 }]
[SetId, System.Action`2[System.Object,System.Int32]]
[GetId, System.Func`2[System.Object,System.Int32]]
[Update, System.Action`2[System.Object,System.EventArgs]]
[SetName, System.Action`2[System.Object,System.String]]
[Name, 田中次郎]

追加したプロパティっぽいものや、メソッドっぽいもの、イベントっぽいものが、型が違うだけのオブジェクトとして追加されているのが判る。

因みに、ExpandoObject は、IDictionary<string, Object> を実装しているので、こんな風に一覧を取ることができるし、要素数を取得したり、要素を削除したりすることもできる。

■ 今回のまとめ

今回も、DynamicObject と ExpandoObject を使ってみた。

dynamic な動作は、実行時にオーバーヘッドが大きいので、そこは要注意だが、応用例を考えてみると面白いのではないだろうか。

2012年12月07日

[C#][dynamic] プラグイン処理

Dynamic

動的処理の一例として、今回はプラグイン処理を行ってみる。

プラグイン処理によって、アプリケーションに対して動的に機能を追加できるようにすることができる。

■ プラグイン処理の例

今回のプラグインは、以下のような規約ベースで動くものとする。

  • 実行中のプログラムがあるフォルダーの下の Plugin と云う名前のフォルダーにある dll ファイルをプラグインのアセンブリと看做す。
  • プラグインのアセンブリの中にある最初の public なクラスをプラグインと見做す。
  • プラグインは、必ず Name と云う名称を表す public なプロパティを持っている。
  • プラグインは、必ず void Run() と云う public なメソッドによって動作する。
・プラグイン処理の実装の例

では、実装してみよう。

・プラグイン側の実装の例

先ずプラグイン側だ。

プラグインは、クラスライブラリとして作成する。

public なクラスを持ち、その中には、public な Name プロパティと public な Run() メソッドを持てば良い。

using System;

public class Plugin
{
    public string Name
    {
        get { return "Sample Plugin"; }
    }

    public void Run()
    {
        Console.WriteLine("This is sample plugin!");
    }
}
・プラグインが組み込まれる本体側の実装の例

次はプラグインが組み込まれる本体側だ。

using System;
using System.IO;
using System.Linq;
using System.Reflection;

class Program
{
    // プラグインのフォルダー名
    const string pluginFolderName = "Plugin";
    // 拡張子が dll のものをプラグインと看做す
    const string pluginFileName = "*.dll";
    
    static void Main()
    {
        // プラグインのフォルダーへのフルパス名を取得し、
        var pluginFolderName = GetPluginFolderName();
        // もしプラグインのフォルダーが在ったら、
        if (Directory.Exists(pluginFolderName))
            // プラグインのフォルダーの各のプラグインのパス名を使って、プラグインを実行
            Directory.GetFiles(pluginFolderName, pluginFileName).ToList().ForEach(Run);
    }

    // プラグインのフォルダーへのフルパス名を取得
    static string GetPluginFolderName()
    {
        // 実行中のこのプログラム自体のアセンブリのパスのフォルダーの下の Plugin フォルダーへのフルパスを取得
        return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + Path.DirectorySeparatorChar + pluginFolderName;
    }

    // プラグインを実行
    static void Run(string pluginPath)
    {
        // アセンブリを読み込み、その中から public な最初のクラスを取り出す
        var pluginType = Assembly.LoadFrom(pluginPath).GetExportedTypes().FirstOrDefault(type => type.IsClass);
        if (pluginType != null)
            // インスタンスを生成し、それをプラグインとして実行
            Run(Activator.CreateInstance(pluginType));
    }

    // プラグインを実行
    static void Run(dynamic plugin)
    {
        Console.WriteLine(plugin.Name); // プラグインの Name を表示し、
        plugin.Run(); // プラグインを Run
    }
}

規約ベースなので、特にプラグイン側は interface 等は持っていないし、本体側も interface で探したりしていない。

規約通りのものを実行する。

双方が特定の interface に依存しているのでなく、規約に依存している。

こういう場合は、dynamic による処理が合っている。

・プラグイン処理の実行例

実行してみよう。

先ずは、Plugin フォルダーを準備しない儘で実行してみる。


何も表示されない。

次に、プログラムを実行しているフォルダーの下に Plugin と云う名前のフォルダーを作成して実行してみる。


矢張り、何も表示されない。

次に、クラスライブラリであるプラグインをビルドし、出来上がった dll ファイルを Plugin フォルダーの直下にコピーし、実行してみる。

Sample Plugin
This is sample plugin!

プラグインが実行された。

Plugin フォルダーの dll を二つにしてみると、

Sample Plugin
This is sample plugin!
Sample Plugin
This is sample plugin!

両方のプラグインが実行される。

・interface を用いたプラグイン処理の実装の例

因みに、interface を用いた場合の例だと次のようになる。

・interface を用いたプラグイン処理の実装の例 - interface

先ず interface をクラス ライブラリとして準備する。

public interface IPlugin
{
    string Name { get; }
    void Run();
}
・interface を用いたプラグイン処理の実装の例 - プラグイン側

プラグイン側で interface のライブラリを参照し、クラスで IPlugin を実装する。

public class Plugin : IPlugin
{
    public string Name
    {
        get { return "Sample Plugin"; }
    }

    public void Run()
    {
        Console.WriteLine("This is sample plugin!");
    }
}
・interface を用いたプラグイン処理の実装の例 - プラグインが組み込まれる本体側

本体側でも interface のライブラリを参照する。

プラグイン側と本体側が同じ interface に依存することになる。

interface を持ったクラスを探して、interface によって Name や Run() にアクセスする。

先の例と違って、dynamic は使わない。

using System;
using System.IO;
using System.Linq;
using System.Reflection;

class Program
{
    // プラグインのフォルダー名
    const string pluginFolderName = "Plugin";
    // 拡張子が dll のものをプラグインと看做す
    const string pluginFileName = "*.dll";
    
    static void Main()
    {
        // プラグインのフォルダーへのフルパス名を取得し、
        var pluginFolderName = GetPluginFolderName();
        // もしプラグインのフォルダーが在ったら、
        if (Directory.Exists(pluginFolderName))
            // プラグインのフォルダーの各のプラグインのパス名を使って、プラグインを実行
            Directory.GetFiles(pluginFolderName, pluginFileName).ToList().ForEach(Run);
    }

    // プラグインのフォルダーへのフルパス名を取得
    static string GetPluginFolderName()
    {
        // 実行中のこのプログラム自体のアセンブリのパスのフォルダーの下の Plugin フォルダーへのフルパスを取得
        return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + Path.DirectorySeparatorChar + pluginFolderName;
    }

    // プラグインを実行
    static void Run(string pluginPath)
    {
        // アセンブリを読み込み、その中から public で IPlugin インタフェイスを持った最初のクラスを取り出す
        var pluginType = Assembly.LoadFrom(pluginPath).GetExportedTypes().FirstOrDefault(type => type.IsClass && typeof(IPlugin).IsAssignableFrom(type));
        if (pluginType != null)
            // インスタンスを生成し、それをプラグインとして実行
            Run((IPlugin)Activator.CreateInstance(pluginType));
    }

    // プラグインを実行
    static void Run(IPlugin plugin)
    {
        Console.WriteLine(plugin.Name); // プラグインの Name を表示し、
        plugin.Run(); // プラグインを Run
    }
}

■ 今回のまとめ

今回は、プラグイン処理をやってみた。

interface を使ってやることもできるが、規約ベースで dynamic を使ってやる方法の方が依存関係がシンプルになる。

dynamic な動作は、実行時にオーバーヘッドが大きいので、そこは要注意だが、応用例を考えてみると面白いのではないだろうか。

2012年12月08日

[C#][Design Pattern] C# による Observer パターンの実装 その1 - 古典的な実装

Observer

Expression を使ってラムダ式のメンバー名を取得する」と云う記事で、ラムダ式の中のメンバーの名前を取得した。

これから数回に渡って、Observer パターンの C# による実装を何種類か例に挙げ、その中で上記を活用していきたい。

今回は、その第一回として、Expression どころか C# の event すら使わない、Observer パターンの最も古典的な実装を見てみよう。

■ Observer パターンとは

先ず Observer パターンの簡単な説明から。

Observer パターンは、GoF による 23 種のデザインパターンの中の一つで、特にポピュラーなものの一つだ。

・参考資料:

■ C# での Observer パターンの実装 1 - 古典的な実装

早速 C# で Observer パターンを実装していこう。

今回は、手始めに上の参考資料で紹介されているような由緒ある方法で実装してみる。

・クラス図

クラス図はこんな感じ。

「C# での Observer パターン 1 - 古典的な実装」のクラス図
「C# での Observer パターン 1 - 古典的な実装」のクラス図
・フレームワーク部 (抽象部)

上半分の青い部分がフレームワーク部分だ。

IObserver が Observable を監視している。

Observable の Add で IObserver を実装したオブジェクト (= オブザーバー) を追加する。

Observable は更新されると、以下の手順で全オブザーバーに更新があったことを通知してオブザーバーの表示が更新されるようにする。

  1. Update() を呼ぶ。
  2. Update() では NotifyObservers() が呼ばれる。
  3. NotifyObservers() は、全オブザーバーの Update() を呼ぶ。
  4. オブザーバーは、Update() の中で表示を更新する。
・アプリケーション部 (具象部)

下半分の赤い部分がアプリケーション部分だ。

Employee は Observable だ。
Model として Number と Name と云う二つのプロパティを持っている。

EmployeeView がオブザーバーだ。
EmployeeView は Employee を表示するための View で、DataSource として Employee が渡されると、Employee のオブザーバーとして自分自身を追加する。

EmployeeView は、表示部として TextControl を二つ持っていて、それぞれに DataSource の Number と Name を表示する。

・実装

それでは、実装していこう。

一部エラー処理を省略する。

・フレームワーク部の実装

先に、フレームワーク部の IObserver と Observable から実装しよう。

こんな感じだ。

// C# による Oberver パターンの実装 その1
using System;
using System.Collections.Generic;

// フレームワーク部

interface IObserver // 更新を監視する側
{
    void Update(Observable observable);
}

abstract class Observable // 更新を監視される側
{
    List<IObserver> observers = new List<IObserver>();

    public void Add(IObserver observer) // オブサーバーの追加
    {
        observers.Add(observer);
    }

    protected void Update() // 更新イベント
    {
        NorifyObservers();
    }

    void NorifyObservers() // 全オブザーバーに更新を通知
    {
        observers.ForEach(observer => observer.Update(this));
    }
}
・アプリケーション部の実装

続いて、アプリケーション部。ここには、Employee と EmployeeView、TextControl、そして Main を含んだクラス Program がある。

// C# による Oberver パターンの実装 その1
using System;

// アプリケーション部

// Model
class Employee : Observable
{
    int number = 0;
    string name = string.Empty;

    public int Number
    {
        get { return number; }
        set
        {
            if (value != number) {
                number = value;
                Update();
            }
        }
    }

    public string Name
    {
        get { return name; }
        set
        {
            if (value != name) {
                name = value;
                Update();
            }
        }
    }
}

class TextControl // テキスト表示用のUI部品 (ダミー)
{
    public string Text
    {
        set { Console.WriteLine("TextControl is updated: {0}", value); }
    }
}

class EmployeeView : IObserver // Employee 用の View
{
    TextControl numberTextControl = new TextControl(); // Number 表示用
    TextControl nameTextControl = new TextControl(); // Name 表示用

    public Employee DataSource
    {
        set { value.Add(this); }
    }

    public void Update(Observable observable) // データソースのどのプロパティが更新されてもここに来る
    {
        var employee = observable as Employee;
        if (employee != null) {
            numberTextControl.Text = employee.Number.ToString();
            nameTextControl.Text = employee.Name;
        }
    }
}

class Program
{
    static void Main()
    {
        var employee = new Employee(); // Model, Observable
        var employeeView = new EmployeeView(); // View, Observer

        employeeView.DataSource = employee; // データバインド
        employee.Number = 100; // Number を変更。employeeView に反映されたかな?
        employee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
    }
}
・実行結果

実行してみると、次のようになる。

TextControl is updated: 100
TextControl is updated:
TextControl is updated: 100
TextControl is updated: 福井太郎

Employee の Number や Name が更新される度に、EmployeeView の Update が呼ばれ、二つの TextControl の表示が更新される。

Number の更新でも Name の更新でも同じ Update が呼ばれている。
ここは、出来ればそれぞれ処理したいところだ。

■ 今回のまとめと次回の予告

さて、Observer パターンの C# による実装の第一回として、古典的な実装を行ってみた。

interface と抽象クラスを用いてフレームワーク部を書いている。

次回は、もう少し C# らしい、event を用いた実装をやってみよう。

2012年12月09日

[C#][Design Pattern] C# による Observer パターンの実装 その2 - event による実装

Observer

前回「C# による Observer パターンの実装 その1 - 古典的な実装」と云う記事で、Observer パターンの C# による実装の第一回として、古典的な実装を行ってみた。

interface と抽象クラスを用いた実装だった訳だが、こうした場合、C# では event を用いるのが普通だろう。

今回は、「C# による Observer パターンの実装 その2」として、event による実装を行ってみる。

■ C# での Observer パターンの実装 2 - event による実装 1

・クラス図 1

クラス図はこんな感じ。

「C# での Observer パターンの実装 2 - event による実装 1」のクラス図
「C# での Observer パターンの実装 2 - event による実装 1」のクラス図

前回 と比べるとフレームワーク部 (抽象部) が無くなっているのが判る。

C# が持っている event が強力なので、それだけで更新によって呼ばれるべき処理が呼べてしまうのだ。

Employee は Model として Number と Name と云う二つのプロパティを持っている。

また、Employee は Update というイベントを持っている。
Employee は更新されると Update イベントを起こす。

Update イベントが起きたときに、もしそのイベント ハンドラーがあれば、それが呼ばれる。

EmployeeView は Employee を表示するための View で、DataSource として Employee が渡されると、Employee の Update イベントにイベント ハンドラーとして Update メソッドを設定する。

EmployeeView は、表示部として TextControl を二つ持っていて、それぞれに DataSource の Number と Name を表示する。

・実装 1

それでは、実装していこう (一部エラー処理を省略)。

// C# による Oberver パターンの実装 - event による実装 1
using System;
using System.Collections.Generic;

// アプリケーション部

// Model: 更新を監視される側
class Employee
{
    public event Action<Employee> Update; // 更新イベント

    int number = 0;
    string name = string.Empty;

    public int Number
    {
        get { return number; }
        set {
            if (value != number) {
                number = value;
                if (Update != null)
                    Update(this);
            }
        }
    }

    public string Name
    {
        get { return name; }
        set {
            if (value != name) {
                name = value;
                if (Update != null)
                    Update(this);
            }
        }
    }
}

class TextControl // テキスト表示用のUI部品 (ダミー)
{
    public string Text {
        set { Console.WriteLine("TextControl is updated: {0}", value); }
    }
}

class EmployeeView // Employee 用の View: 更新を監視する側
{
    TextControl numberTextControl = new TextControl(); // Number 表示用
    TextControl nameTextControl = new TextControl(); // Name 表示用

    public Employee DataSource
    {
        set {
            value.Update += Update; // データソースの更新イベントにハンドラを設定
        }
    }

    void Update(Employee employee) // データソースのどのプロパティが更新されてもここに来る
    {
        numberTextControl.Text = employee.Number.ToString();
        nameTextControl.Text = employee.Name;
    }
}

class Program
{
    static void Main()
    {
        var employee = new Employee(); // Model, Observable
        var employeeView = new EmployeeView(); // View, Observer

        employeeView.DataSource = employee; // データバインド
        employee.Number = 100; // Number を変更。employeeView に反映されたかな?
        employee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
    }
}
・実行結果

実行してみると、次のようになる。

前回と同じ結果だ。

TextControl is updated: 100
TextControl is updated:
TextControl is updated: 100
TextControl is updated: 福井太郎

今回も矢張り、Employee の Number や Name が更新される度に、EmployeeView の Update が呼ばれ、二つの TextControl の表示が更新される。

■ C# での Observer パターンの実装 2 - event による実装 2

上では、Number と Name のどちらが更新されても両方の TextControl が更新されている。

これは、Number と Name のどちらが更新されても同じイベントが起きるようになっている為だ。

更新イベントを分けて、それぞれの TextControl が更新されるように改良してみよう 。

・クラス図 2

クラス図はこうなる。

「C# での Observer パターンの実装 2 - event による実装 2」のクラス図
「C# での Observer パターンの実装 2 - event による実装 2」のクラス図

一つだった Employee 側の Update イベントが、UpdateNumber と UpdateName に分かれている。

・実装 2

それでは、実装してみよう (一部エラー処理を省略)。

// C# による Oberver パターンの実装 - event による実装 2
using System;
using System.Collections.Generic;

// アプリケーション部

// Model: 更新を監視される側
class Employee
{
    public event Action<Employee> UpdateNumber; // Number 更新イベント
    public event Action<Employee> UpdateName; // Name 更新イベント 

    int number = 0;
    string name = string.Empty;

    public int Number
    {
        get { return number; }
        set {
            if (value != number) {
                number = value;
                if (UpdateNumber != null)
                    UpdateNumber(this);
            }
        }
    }

    public string Name
    {
        get { return name; }
        set {
            if (value != name) {
                name = value;
                if (UpdateName != null)
                    UpdateName(this);
            }
        }
    }
}

class TextControl // テキスト表示用のUI部品 (ダミー)
{
    public string Text {
        set { Console.WriteLine("TextControl is updated: {0}", value); }
    }
}

class EmployeeView // Employee 用の View: 更新を監視する側
{
    TextControl numberTextControl = new TextControl(); // Number 表示用
    TextControl nameTextControl = new TextControl(); // Name 表示用

    public Employee DataSource
    {
        set {
            // それぞれの更新イベントでそれぞれ TextControl を更新
            value.UpdateNumber += employee => numberTextControl.Text = employee.Number.ToString();
            value.UpdateName += employee => nameTextControl.Text = employee.Name;
        }
    }
}

class Program
{
    static void Main()
    {
        var employee = new Employee(); // Model, Observable
        var employeeView = new EmployeeView(); // View, Observer
    
        employeeView.DataSource = employee; // データバインド
        employee.Number = 100; // Number を変更。employeeView に反映されたかな?
        employee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
    }
}
・実行結果

実行してみると、次のようになる。

Number と Name それぞれの更新で、それぞれの TextControl が更新されている。

TextControl is updated: 100
TextControl is updated: 福井太郎

それぞれで更新されるようになったのは良いが、Employee 側でプロパティ毎にイベントを用意しなければいけない。

これではプロパティの数が増えてくると、コードが複雑になりそうだ。

■ 今回のまとめと次回の予告

さて、Observer パターンの C# による実装の第二回として、C# の event を用いた実装を行ってみた。

前回のようなフレームワーク部がない。

次回は、フレームワーク部を復活させてみる。

2012年12月17日

[C#][Design Pattern] C# による Observer パターンの実装 その3 - 複数のプロパティの更新イベントをフレームワーク側で振り分け

Observer

前回「C# による Observer パターンの実装 その2 - event による実装」と云う記事で、Observer パターンの C# による実装の第二回として、C# の event を用いた実装を行ってみた。

event を用いることで、より C# らしい実装となった。

C# での Observer パターンの実装 1 - 古典的な実装」のフレームワーク部にあたる部分を C# の event 機構で行ってしまっている訳だ。

すっきりとした実装となったが、監視される側 (Observer 側) でプロパティ毎にイベントを用意しなければいけない、という面があった。

「プロパティの数が増えてくると、コードが複雑になりそう」と云う点が気になった。

そこで、今回は、「C# による Observer パターンの実装 その3 - 複数のプロパティの更新イベントをフレームワーク側で振り分け」として、その2で無くなったフレームワーク部を復活させ、そちらに複数のイベント処理を任せてみる。

■ C# での Observer パターンの実装 3

・クラス図 1

今回のクラス図はこんな感じだ。

「C# での Observer パターンの実装 3」のクラス図
「C# での Observer パターンの実装 3」のクラス図

C# による Observer パターンの実装 その1」のときと同様、上半分の青い部分がフレームワーク部、下半分の赤い部分がアプリケーション部だ。

・フレームワーク部 (抽象部)

先ずは、フレームワーク部の方から。

Observable (更新を監視される側) は、今回は、一つだけの Update というイベントを持つ。
どのプロパティが更新されてもこのイベントを起こす。

但し、プロパティ名を引数で受け取るようになっている。

RaiseUpdate と云うメソッドに文字列でプロパティ名を渡すことで、Update イベントが起きる。

また、Observer (更新を監視する側) は、 DataSource として Observable が渡されると、Observable の Update イベントにイベント ハンドラーとして Update メソッドを設定する。

AddUpdateAction は、プロパティの名称毎に処理を登録するメソッドだ。

Observable 側で更新イベントが起きてイベント ハンドラーの Update が呼ばれると、プロパティの名毎の処理が呼ばれる仕組みだ。

最後に、ObjectExtensions だが、このクラスは、オブジェクトのプロパティ名からプロパティの値を取り出すために使われるメソッド Eval を持つ。
このメソッド Eval は、Observer の中で、プロパティ名からプロパティの値を取り出すために使われる。

・アプリケーション部 (具象部)

Employee は Observable だ。
今回も Model として Number と Name と云う二つのプロパティを持っている。

Update イベントは、ここには無い。

各プロパティが更新されたときに、RaiseUpdate をプロパティ名を引数にして呼ぶことで、ベース クラス Observable の持つ Update イベントを起こす。

EmployeeView はオブザーバーだ。

今回も、EmployeeView は Employee を表示するための View で、AddUpdateAction メソッドを使って、プロパティ毎の更新処理を登録する。

・実装

それでは、実装していこう (一部エラー処理を省略)。

・フレームワーク部の実装

フレームワーク部の ObjectExtensions と Observable、Observer から実装しよう。

// C# による Oberver パターンの実装 その3
using System;
using System.Collections.Generic;

// フレームワーク部

public static class ObjectExtensions
{
    // オブジェクトの指定された名前のプロパティの値を取得
    public static object Eval(this object item, string propertyName)
    {
        var propertyInfo = item.GetType().GetProperty(propertyName);
        return propertyInfo == null ? null : propertyInfo.GetValue(item, null);
    }
}

abstract class Observable // 更新を監視される側
{
    public event Action<string> Update;

    protected void RaiseUpdate(string propertyName)
    {
        if (Update != null)
            Update(propertyName);
    }
}

abstract class Observer // 更新を監視する側
{
    Dictionary<string, Action<object>> updateExpressions = new Dictionary<string, Action<object>>();
    Observable dataSource = null;

    public Observable DataSource
    {
        set {
            dataSource = value;
            value.Update += Update;
        }
    }

    protected void AddUpdateAction(string propertyName, Action<object> updateAction)
    {
        updateExpressions[propertyName] = updateAction;
    }

    void Update(string propertyName)
    {
        Action<object> updateAction;
        if (updateExpressions.TryGetValue(propertyName, out updateAction))
            updateAction(dataSource.Eval(propertyName));
    }
}
・アプリケーション部の実装

続いて、アプリケーション部。Employee と EmployeeView、TextControl、そして Main を含んだクラス Program だ。

// C# による Oberver パターンの実装 その3
using System;

// アプリケーション部

// Model
class Employee : Observable
{
    int number = 0;
    string name = string.Empty;

    public int Number
    {
        get { return number; }
        set {
            if (value != number) {
                number = value;
                RaiseUpdate("Number");
            }
        }
    }

    public string Name
    {
        get { return name; }
        set {
            if (value != name) {
                name = value;
                RaiseUpdate("Name");
            }
        }
    }
}

class TextControl // テキスト表示用のUI部品 (ダミー)
{
    public string Text
    {
        set { Console.WriteLine("TextControl is updated: {0}", value); }
    }
}

class EmployeeView : Observer // Employee 用の View
{
    TextControl numberTextControl = new TextControl(); // Number 表示用
    TextControl nameTextControl = new TextControl(); // Name 表示用

    public EmployeeView()
    {
        AddUpdateAction("Number", number => numberTextControl.Text = ((int)number).ToString());
        AddUpdateAction("Name", name => nameTextControl.Text = (string)name);
    }
}

class Program
{
    static void Main()
    {
        var employee = new Employee(); // Model, Observable
        var employeeView = new EmployeeView(); // View, Observer

        employeeView.DataSource = employee; // データバインド
        employee.Number = 100; // Number を変更。employeeView に反映されたかな?
        employee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
    }
}
・クラス図 2

先のクラス図に、引数と戻り値の型を書き加えたものを示す。

「C# での Observer パターンの実装 3」のクラス図
「C# での Observer パターンの実装 3」のクラス図
・実行結果

実行してみると、次のようになる。

前回の二つ目と同じ結果だ。

TextControl is updated: 100
TextControl is updated: 福井太郎

Number と Name それぞれの更新で、それぞれの TextControl が更新されている。

■ 今回のまとめと次回の予告

今回は、フレームワーク部で、プロパティ毎の更新処理が呼ばれるようにした。

結果として、アプリケーション部の Employee でプロパティ毎に更新イベントを用意する必要がなくなった。

しかし、アプリケーション部の一部に文字列でプロパティを指定する部分が出来てしまった。

Employee の中の、RaiseUpdate("Number") と RaiseUpdate("Name")、EmployeeView の中の AddUpdateAction("Number", ...) と AddUpdateAction("Name", ...) だ。

次回は、この問題を解決しよう。

2012年12月18日

[C#][Design Pattern][式木] C# による Observer パターンの実装 その4 - Expression を使ってプロパティの指定をタイプセーフに

Observer

前回「C# による Observer パターンの実装 その3 - 複数のプロパティの更新イベントをフレームワーク側で振り分け」と云う記事で、Observer パターンの C# による実装の第三回として、フレームワーク部で、プロパティ毎の更新処理が呼ばれるようにした。

結果として、アプリケーション部の Employee でプロパティ毎に更新イベントを用意する必要がなくなった。

しかし、アプリケーション部の一部に文字列でプロパティを指定する部分が出来てしまった。

そこで、今回は、「C# による Observer パターンの実装 その4 - Expression を使ってプロパティの指定をタイプセーフに」として、文字列でプロパティを指定する部分を解消してみよう。

■ C# での Observer パターンの実装 4

以前、「Expression を使ってラムダ式のメンバー名を取得する」と云う記事で、Expression を使ってラムダ式からプロパティ名を取得したことがある。

これを用いることが出来る。

・フレームワーク部 - ObjectExtensions への追加

前回の ObjectExtensions に、件の記事から、Expression からメンバー名を取得する部分を持ってこよう。

using System;
using System.Linq.Expressions;

public static class ObjectExtensions
{
    // オブジェクトの指定された名前のプロパティの値を取得 (前回作成)
    public static object Eval(this object item, string propertyName)
    {
        var propertyInfo = item.GetType().GetProperty(propertyName);
        return propertyInfo == null ? null : propertyInfo.GetValue(item, null);
    }

    // Expression からメンバー名を取得 (「Expression を使ってラムダ式のメンバー名を取得する」で作成)
    public static string GetMemberName<ObjectType, MemberType>(this ObjectType @this, Expression<Func<ObjectType, MemberType>> expression)
    {
        return ((MemberExpression)expression.Body).Member.Name;
    }

    // Expression からメンバー名を取得 (「Expression を使ってラムダ式のメンバー名を取得する」で作成)
    public static string GetMemberName<MemberType>(Expression<Func<MemberType>> expression)
    {
        return ((MemberExpression)expression.Body).Member.Name;
    }
}
・フレームワーク部 - Observable と Observer の変更

そして、前回のフレームワーク部の Observable と Observer のソースコード:

// C# による Oberver パターンの実装 その3 (前回)
using System;
using System.Collections.Generic;

// フレームワーク部

abstract class Observable // 更新を監視される側
{
    public event Action<string> Update;

    protected void RaiseUpdate(string propertyName)
    {
        if (Update != null)
            Update(propertyName);
    }
}

abstract class Observer // 更新を監視する側
{
    Dictionary<string, Action<object>> updateExpressions = new Dictionary<string, Action<object>>();
    Observable dataSource = null;

    public Observable DataSource
    {
        set {
            dataSource = value;
            value.Update += Update;
        }
    }

    protected void AddUpdateAction(string propertyName, Action<object> updateAction)
    {
        updateExpressions[propertyName] = updateAction;
    }

    void Update(string propertyName)
    {
        Action<object> updateAction;
        if (updateExpressions.TryGetValue(propertyName, out updateAction))
            updateAction(dataSource.Eval(propertyName));
    }
}

これの、文字列でプロパティ名を受けている箇所に、Expression で受けるメソッドを追加する。

こうだ。

// C# による Oberver パターンの実装 その4
using System;
using System.Collections.Generic;
using System.Linq.Expressions;

abstract class Observable // 更新を監視される側
{
    public event Action<string> Update;

    protected void RaiseUpdate<PropertyType>(Expression<Func<PropertyType>> propertyExpression)
    {
        RaiseUpdate(ObjectExtensions.GetMemberName(propertyExpression));
    }

    void RaiseUpdate(string propertyName)
    {
        if (Update != null)
            Update(propertyName);
    }
}

abstract class Observer<ObservableType> where ObservableType : Observable // 更新を監視する側
{
    Dictionary<string, Action<object>> updateExpressions = new Dictionary<string, Action<object>>();
    ObservableType dataSource = null;

    public ObservableType DataSource
    {
        set {
            dataSource = value;
            value.Update += Update;
        }
    }

    protected void AddUpdateAction<PropertyType>(Expression<Func<ObservableType, PropertyType>> propertyExpression, Action<object> updateAction)
    {
        AddUpdateAction(dataSource.GetMemberName(propertyExpression), updateAction);
    }

    void AddUpdateAction(string propertyName, Action<object> updateAction)
    {
        updateExpressions[propertyName] = updateAction;
    }

    void Update(string propertyName)
    {
        Action<object> updateAction;
        if (updateExpressions.TryGetValue(propertyName, out updateAction))
            updateAction(dataSource.Eval(propertyName));
    }
}
・アプリケーション部 - Employee と EmployeeView の変更

すると、前回のアプリケーション部の Employee と EmployeeView のソースコード:

// C# による Oberver パターンの実装 その3 (前回)
using System;

// アプリケーション部

// Model
class Employee : Observable
{
    int number = 0;
    string name = string.Empty;

    public int Number
    {
        get { return number; }
        set {
            if (value != number) {
                number = value;
                RaiseUpdate("Number"); // ☆ 文字列でプロパティを指定
            }
        }
    }

    public string Name
    {
        get { return name; }
        set {
            if (value != name) {
                name = value;
                RaiseUpdate("Name"); // ☆ 文字列でプロパティを指定
            }
        }
    }
}

class TextControl // テキスト表示用のUI部品 (ダミー)
{
    public string Text
    {
        set { Console.WriteLine("TextControl is updated: {0}", value); }
    }
}

class EmployeeView : Observer // Employee 用の View
{
    TextControl numberTextControl = new TextControl(); // Number 表示用
    TextControl nameTextControl = new TextControl(); // Name 表示用

    public EmployeeView()
    {
        // ☆ AddUpdateAction の第一引数で文字列でプロパティを指定
        AddUpdateAction("Number", number => numberTextControl.Text = ((int)number).ToString());
        AddUpdateAction("Name", name => nameTextControl.Text = (string)name);
    }
}

これが、こうなる。

// C# による Oberver パターンの実装 その4
using System;

// アプリケーション部

// Model
class Employee : Observable
{
    int number = 0;
    string name = string.Empty;

    public int Number
    {
        get { return number; }
        set {
            if (value != number) {
                number = value;
                RaiseUpdate(() => Number); // ☆ RaiseUpdate がタイプセーフに
            }
        }
    }

    public string Name
    {
        get { return name; }
        set {
            if (value != name) {
                name = value;
                RaiseUpdate(() => Name); // ☆ RaiseUpdate がタイプセーフに
            }
        }
    }
}

class TextControl // テキスト表示用のUI部品 (ダミー)
{
    public string Text
    {
        set { Console.WriteLine("TextControl is updated: {0}", value); }
    }
}

class EmployeeView : Observer<Employee> // Employee 用の View
{
    TextControl numberTextControl = new TextControl(); // Number 表示用
    TextControl nameTextControl = new TextControl(); // Name 表示用

    public EmployeeView()
    {
        // ☆ AddUpdateAction の第一引数がタイプセーフに
        AddUpdateAction(employee => employee.Number, number => numberTextControl.Text = ((int)number).ToString());
        AddUpdateAction(employee => employee.Name, name => nameTextControl.Text = (string)name);
    }
}

文字列でプロパティを指定している箇所が消えた。

・Main を含んだクラス Program

Main を含んだクラス Program は、変更なし。

class Program
{
    static void Main()
    {
        var employee = new Employee(); // Model, Observable
        var employeeView = new EmployeeView(); // View, Observer

        employeeView.DataSource = employee; // データバインド
        employee.Number = 100; // Number を変更。employeeView に反映されたかな?
        employee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
    }
}
・実行結果

実行結果も変わらない。

TextControl is updated: 100
TextControl is updated: 福井太郎

矢張り、Number と Name それぞれの更新で、それぞれの TextControl が更新されている。

・クラス図

さてクラス図はこうなった。
前回と余り変わらない。

「C# での Observer パターンの実装 4」のクラス図
「C# での Observer パターンの実装 4」のクラス図
「C# での Observer パターンの実装 4」のクラス図 (引数と戻り値の型を書き加えたもの)
「C# での Observer パターンの実装 4」のクラス図 (引数と戻り値の型を書き加えたもの)

例によって、青い部分がフレームワーク部、赤い部分がアプリケーション部だ。

■ 今回のまとめ

今回は、前回のものに対して、プロパティを文字列で指定している部分を改良した。

そうすることで、アプリケーション部でプロパティを指定する箇所がタイプセーフになった。

アプリケーション部が随分すっきりと書けるようになったのが判る。

■ 次回予告

さて次回だが、今回 Expression を使って行ったのと同様のことを別の方法で実現してみたい。

お楽しみに。

2012年12月19日

[Windows 8][Windows ストア アプリ] Windows 8 ストア アプリ開発資料リンク集

Windows 8

Windows 8 の ストア アプリ開発を始めるにあたり、参考にさせていただいたサイトをご紹介する。

2012年12月20日

[Windows 8][Windows ストア アプリ][C#] ポータブル クラス ライブラリに関する検証

Windows 8 Store apps Advent Calendar の 20日目のエントリー。

以前、「Windows Store アプリと Windows Phone アプリ、Silverlight アプリ、WPF アプリでソースコードを共通化する方法に関する記事」と云う記事でポータブル クラス ライブラリに関して少しだけご紹介した。

今回は、ポータブル クラス ライブラリについて、更に調べてみよう。

■ アプリケーションの種類と .NET Framework の違い

Windows ストア アプリで参照している .NET は、他のアプリケーションで参照しているものと少し異なる。

その様子を先ず確認してみよう。

現時点で最新の環境で何種類かのアプリケーションをデフォルトで追加してみて、参照している .NET を見てみる。

・コンソール アプリケーションとクラス ライブラリの場合 (.NET Framework 4.5)
コンソール アプリケーションとクラス ライブラリで参照している .NET
コンソール アプリケーションとクラス ライブラリで参照している .NET
・WPF の場合 (.NET Framework 4.5)
WPF アプリケーションで参照している .NET
WPF アプリケーションで参照している .NET
・Silverlight の場合 (Silverlight 5)
Silverlight アプリケーションと Silverlight クラス ライブラリで参照している .NET
Silverlight アプリケーションと Silverlight クラス ライブラリで参照している .NET
・Windows Phone の場合 (Windows Phone OS 8.0)
Windows Phone アプリと Windows Phone クラス ライブラリで参照している .NET
Windows Phone アプリと Windows Phone クラス ライブラリで参照している .NET

「.NET for Windows Phone」となっている。

・Windows ストア アプリの場合
Windows ストア アプリとWindows ストア クラス ライブラリで参照している .NET
Windows ストア アプリとWindows ストア クラス ライブラリで参照している .NET

こちらでは、「.NET for Windows Store apps」となっているのが判る。

■ IsSubclassOf 等による検証

つまり、これらで参照している .NET は、全てが共通な訳ではない。

コア部分は共通なのだろうか?

私が試してみたところ、System 名前空間の付近でも微妙な違いがあるようだ。

今回は、以下のようなコードを用いて、この辺りを検証してみたい。

// コンソール アプリケーション
// .NET Framework 4.5

using System.Reflection; // ※ GetRuntimeProperties に必要

class Program
{
    class Super { }
    class Sub : Super
    {
        public int Number { get; set; }
        public string Name { get; set; }
    }

    static void Test()
    {
        var sub = new Sub();

        bool result1 = sub.GetType().IsSubclassOf(typeof(Super));
        bool result2 = sub.GetType().IsAssignableFrom(typeof(Super));
        bool result3 = sub is Super;

        var properties1 = typeof(Sub).GetProperties();
        var properties2 = typeof(Sub).GetRuntimeProperties();
    }
        
    static void Main()
    {
        Test();
    }
}

これはコンソール アプリケーションのソースコードだが、正常にコンパイルでき、正常に動作する。

Visual Studio のデバッガーで値を確認してみると、次のようになった。

コンソール アプリケーションの場合の実行結果 1
コンソール アプリケーションの場合の実行結果 1
コンソール アプリケーションの場合の実行結果 2
コンソール アプリケーションの場合の実行結果 2
コンソール アプリケーションの場合の実行結果 3
コンソール アプリケーションの場合の実行結果 3
・WPF と Silverlight の場合

同様のことを、WPF と Silverlight の場合で試してみると次のようになる。

先ず WPF から。

// WPF アプリケーション
// .NET Framework 4.5

using System.Windows;

namespace WpfApplication
{
    using System.Reflection; // ※ GetRuntimeProperties に必要
    
    public partial class App : Application
    {
        class Super { }
        class Sub : Super
        {
            public int Number { get; set; }
            public string Name { get; set; }
        }

        static void Test()
        {
            var sub = new Sub();

            bool result1 = sub.GetType().IsSubclassOf(typeof(Super)); // ○ OK
            bool result2 = sub.GetType().IsAssignableFrom(typeof(Super)); // ○ OK
            bool result3 = sub is Super; // ○ OK

            var properties1 = typeof(Sub).GetProperties(); // ○ OK
            var properties2 = typeof(Sub).GetRuntimeProperties(); // ○ OK
        }
        
        public App()
        {
            Test();
        }
    }
}

WPF では問題なく、全てコンパイルでき、正常に動作する。

参照している .NET が同じ .NET Framework 4.5 なので、当たり前と云えば当たり前だ。

次に Silverlight。

// Silverlight 5

using System;
using System.Windows;

namespace SilverlightApplication
{
    using System.Reflection; // ※ GetRuntimeProperties に必要

    public partial class App : Application
    {
        class Super { }
        class Sub : Super
        {
            public int Number { get; set; }
            public string Name { get; set; }
        }

        static void Test()
        {
            var sub = new Sub();

            bool result1 = sub.GetType().IsSubclassOf(typeof(Super)); // ○ OK
            bool result2 = sub.GetType().IsAssignableFrom(typeof(Super)); // ○ OK
            bool result3 = sub is Super; // ○ OK

            var properties1 = typeof(Sub).GetProperties(); // ○ OK
            var properties2 = typeof(Sub).GetRuntimeProperties(); // × コンパイル エラー
        }

        public App()
        {
            Test();

            // ... 以下省略 ...
        }

        // ... 以下省略 ...
    }
}

Silverlight の方は、次のようなコンパイル エラーになる。

  • エラー 1 'System.Type' に 'GetRuntimeProperties' の定義が含まれておらず、型 'System.Type' の最初の引数を受け付ける拡張メソッドが見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足しています。

この GetRuntimeProperties は、 System.Reflection 名前空間の RuntimeReflectionExtensions クラスが持つ拡張メソッドだ。

.NET Framework 4.5 で使えるようになったものだが、Silverlight では使えないようだ。

・Windows Phone の場合

ちなみに、Windows Phone では次のようになる。

// Windows Phone OS 8.0

using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using PhoneApp.Resources;
using System.Diagnostics;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Navigation;

namespace PhoneApp
{
    using System;
    using System.Reflection; // ※ GetRuntimeProperties に必要
    
    public partial class App : Application
    {
        class Super { }
        class Sub : Super
        {
            public int Number { get; set; }
            public string Name { get; set; }
        }

        static void Test()
        {
            var sub = new Sub();

            bool result1 = sub.GetType().IsSubclassOf(typeof(Super)); // ○ OK
            bool result2 = sub.GetType().IsAssignableFrom(typeof(Super)); // ○ OK
            bool result3 = sub is Super; // ○ OK

            var properties1 = typeof(Sub).GetProperties(); // ○ OK
            var properties2 = typeof(Sub).GetRuntimeProperties(); // ○ OK
        }

        public App()
        {
            Test();

            // ... 以下省略 ...
        }

        // ... 以下省略 ...
    }
}

全て問題なくコンパイル・実行できる。

・Windows ストア アプリの場合

さて、Windows ストア アプリではどうなるだろうか。

こうなるのだ。

// Windows ストア アプリ

using System;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace WindowsStoreApp
{
    using System.Reflection; // ※ GetRuntimeProperties に必要
    
    sealed partial class App : Application
    {
        class Super { }
        class Sub : Super
        {
            public int Number { get; set; }
            public string Name { get; set; }
        }

        static void Test()
        {
            var sub = new Sub();

            bool result1 = sub.GetType().IsSubclassOf(typeof(Super)); // × コンパイル エラー
            bool result2 = sub.GetType().IsAssignableFrom(typeof(Super)); // × コンパイル エラー
            bool result3 = sub is Super; // ○ OK

            var properties1 = typeof(Sub).GetProperties(); // × コンパイル エラー
            var properties2 = typeof(Sub).GetRuntimeProperties(); // ○ OK
        }

        public App()
        {
            Test();

            // ... 以下省略 ...

            this.InitializeComponent();
            this.Suspending += OnSuspending;
        }

        // ... 以下省略 ...
    }
}

次のようなコンパイル エラーとなる。

  • エラー 1 'System.Type' に 'IsSubclassOf' の定義が含まれておらず、型 'System.Type' の最初の引数を受け付ける拡張メソッドが見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足しています。
  • エラー 2 'System.Type' に 'IsAssignableFrom' の定義が含まれておらず、型 'System.Type' の最初の引数を受け付ける拡張メソッドが見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足しています。
  • エラー 3 'System.Type' に 'GetProperties' の定義が含まれておらず、型 'System.Type' の最初の引数を受け付ける拡張メソッドが見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足しています。

なんと、System 名前空間の Type 型が IsSubclassOf や IsAssignableFrom を持っていないようだ。

実際に調べてみると、Windows ストア アプリが参照している System 名前空間の Type 型は以下の public メンバーを持っている。

// Windows ストア アプリが参照している System 名前空間の Type 型
// アセンブリ System.Runtime.dll, v4.0.0.0
// Framework\.NETCore\v4.5\System.Runtime.dll
namespace System
{
    public abstract class Type
    {
        public static readonly object Missing;

        public abstract string AssemblyQualifiedName { get; }
        public abstract Type DeclaringType { get; }
        public abstract string FullName { get; }
        public abstract int GenericParameterPosition { get; }
        public abstract Type[] GenericTypeArguments { get; }
        public bool HasElementType { get; }
        public bool IsArray { get; }
        public bool IsByRef { get; }
        public abstract bool IsConstructedGenericType { get; }
        public abstract bool IsGenericParameter { get; }
        public bool IsNested { get; }
        public bool IsPointer { get; }

        public abstract string Name { get; }
        public abstract string Namespace { get; }
        public virtual RuntimeTypeHandle TypeHandle { get; }

        public override bool Equals(object o);
        public bool Equals(Type o);
        public abstract int GetArrayRank();
        public abstract Type GetElementType();
        public abstract Type GetGenericTypeDefinition();
        public override int GetHashCode();
        public static Type GetType(string typeName);
        public static Type GetType(string typeName, bool throwOnError);
        public static Type GetTypeFromHandle(RuntimeTypeHandle handle);
        public abstract Type MakeArrayType();
        public abstract Type MakeArrayType(int rank);
        public abstract Type MakeByRefType();
        public abstract Type MakeGenericType(params Type[] typeArguments);
        public abstract Type MakePointerType();
        public override string ToString();
    }
}

IsSubclassOf、IsAssignableFrom、GetProperties 等が見当たらない。

一方、通常の .NET Framework 4.5 の名前空間の Type 型は遥かに多くの public メンバーを持っている。

// 通常の .NET Framework 4.5 の名前空間の Type 型
// アセンブリ mscorlib.dll, v4.0.0.0
// Framework\.NETFramework\v4.5\mscorlib.dll
namespace System
{
    public abstract class Type : MemberInfo, _Type, IReflect
    {
        bool IsSubclassOf(Type c);
        bool IsAssignableFrom(Type c);
        PropertyInfo[] GetProperties();
        
        // ...その他遥かに多くのメンバー...
    }
}

先ず、アセンブリが異なる。Windows ストア アプリの方は、 mscorlib.dll ではなく System.Runtime.dll だった。

また、こちらは Windows ストア アプリの方の Type と異なり、「class Type : MemberInfo, _Type, IReflect」となっているのが判る。

この中の _Type は実は interface で、以下のように IsSubclassOf、IsAssignableFrom、GetProperties を含む多くのメンバーを持っているのだ。

// 通常の .NET Framework 4.5 の名前空間の Type 型が実装している interface _Type
namespace System.Runtime.InteropServices
{
    public interface _Type
    {
        bool IsSubclassOf(Type c);
        bool IsAssignableFrom(Type c);
        PropertyInfo[] GetProperties();
        
        // ...その他多くのメンバー...
    }
}

■ ポータブル クラス ライブラリによる解決

では、ポータブル クラス ライブラリを利用した場合はどうなるだろうか。

・ポータブル クラス ライブラリの作成

先ず、ポータブル クラス ライブラリを作成する。

ポータブル クラス ライブラリの作成
ポータブル クラス ライブラリの作成

今回は、ターゲット フレームワークとしてデフォルトの儘、.NET Framework 4.5、Silverlight 4 以上、Windows Phone 7 以上、.NET for Windows Store apps を選ぶ。

ポータブル クラス ライブラリの作成
ポータブル クラス ライブラリの作成

ソースコードは以下の通り。

Type 型の IsSubclassOf、IsAssignableFrom、GetProperties をそれぞれ呼ぶだけのメソッドを用意することにする。

// ポータブル クラス ライブラリ
//
// ターゲット フレームワーク:
// ・.NET Framework 4.5
// ・Silverlight 4 以上
// ・Windows Phone 7 以上
// ・.NET for Windows Store apps

namespace PortableClassLibrary
{
    using System;
    using System.Reflection;

    public static class TestClass
    {
        public static bool IsSubclassOf(Type sub, Type super)
        {
            return sub.IsSubclassOf(super);
        }

        public static bool IsAssignableFrom(Type type1, Type type2)
        {
            return type1.IsAssignableFrom(type2);
        }

        public static PropertyInfo[] GetProperties(Type type)
        {
            return type.GetProperties();
        }
    }
}

これは問題なくコンパイルできる。

ちなみに、このポータブル クラス ライブラリで参照している Type 型は次のようなものだ。

// このポータブル クラス ライブラリが参照している Type 型
// アセンブリ mscorlib.dll, v2.0.5.0
// Framework\.NETPortable\v4.0\Profile\Profile4\mscorlib.dll
namespace System
{
    public abstract class Type : MemberInfo
    {
        bool IsSubclassOf(Type c);
        bool IsAssignableFrom(Type c);
        PropertyInfo[] GetProperties();
        
        // ...その他多くのメンバー...
    }
}

こちらは、IsSubclassOf、IsAssignableFrom、GetProperties 等を持っている。_Type interface は持っていない。

また、このポータブル クラス ライブラリが参照している .NET はこうなっている。

このポータブル クラス ライブラリが参照している .NET
このポータブル クラス ライブラリが参照している .NET

「.NET Portable Subset」となっている。

・作成したポータブル クラス ライブラリの参照

では、早速このポータブル クラス ライブラリを各アプリケーション (コンソール アプリケーション、WPF アプリケーション、Silverlight アプリケーション、Windows Phone アプリ、Windows ストア アプリ) でそれぞれ参照してみよう。

ポータブル クラス ライブラリの参照
ポータブル クラス ライブラリの参照

そして、各アプリケーションで、以下のようにこのポータブル クラス ライブラリの三つのメソッド、IsSubclassOf、IsAssignableFrom、GetProperties を呼んでみる。

    // 参照したポータブル クラス ライブラリの利用
    bool result4 = PortableClassLibrary.TestClass.IsSubclassOf(sub.GetType(), typeof(Super));
    bool result5 = PortableClassLibrary.TestClass.IsAssignableFrom(sub.GetType(), typeof(Super));
    var properties3 = PortableClassLibrary.TestClass.GetProperties(sub.GetType());

すると、コンソール アプリケーション、WPF アプリケーション、Silverlight アプリケーション、Windows Phone アプリ、Windows ストア アプリの何れでも、コンパイルでき、正常に動作する。

つまり、ポータブル クラス ライブラリにであれば、この部分のコードは共通化でき、且つそれぞれのアプリケーションから問題なく呼べるのだ。

■ 今回のまとめ

今回は、ポータブル クラス ライブラリに関して、調査をしてみた。

アプリケーションの種類によって、参照している .NET が異なるので、.NET のコア部分を使ったソースコードでも共通のものが使えないことがあることが判った。

ポータブル クラス ライブラリを使うことで、そのような部分のソースコードをより共通化することができるだろう。

2012年12月21日

[Windows 8] Windows 8 お役立ち記事リンク集

Windows 8 お役立ち記事リンク集

新しい Windows 8 を使ってみたい、でもデザインも大幅に変わったし、大丈夫かな?
そんなときに便利な、Windows 8 にまつわるお役立ち記事をまとめて、リンク集にしてみました。
執筆を担当するのは、マイクロソフトに最優秀技術者として表彰された「Microsoft MVP」* のメンバー!
Windows 8 へのアップグレード方法から便利なショートカットの紹介まで、幅広いトピックが満載です。
Windows 8 のことで困ったり、いまさら聞けないことがあったら、ぜひこれらの記事を参考にしてみてください。

(このブログも参加しています)

Microsoft MVP ロゴ *Microsoft MVP (Most Valuable Professional) アワード プログラムは、マイクロソフトの製品やテクノロジーに関する豊富な知識と経験を持ち、オンラインまたはオフラインのコミュニティや、メディアなどを通して、その優れた能力を幅広いユーザーと共有している個人を表彰するものです。

(敬称略)

2012年12月22日

[C#][Design Pattern] C# による Observer パターンの実装 その5 - Caller Info を使ってプロパティの指定をよりシンプルに

Observer

前回「C# による Observer パターンの実装 その4 - Expression を使ってプロパティの指定をタイプセーフに」と云う記事で、Observer パターンの C# による実装の第四回として、Expression を用いることで、前々回文字列でプロパティを指定していた部分をタイプセーフにしてみた。

今回は、それに対する補足だ。
前回の Expression による遣り方とは別の方法で更にアプリケーション部をシンプルにしてみたい。
C# 5 の新機能の Caller Info を使った方法だ。

■ 前回のソースコード

先ず前回のフレームワーク部の Observable のソースコードを再掲する。

・フレームワーク部 - Observable (前回)
// C# による Oberver パターンの実装 その4
using System;
using System.Collections.Generic;
using System.Linq.Expressions;

abstract class Observable // 更新を監視される側
{
    public event Action<string> Update;

    protected void RaiseUpdate<PropertyType>(Expression<Func<PropertyType>> propertyExpression)
    {
        RaiseUpdate(ObjectExtensions.GetMemberName(propertyExpression));
    }

    void RaiseUpdate(string propertyName)
    {
        if (Update != null)
            Update(propertyName);
    }
}

Observable の RaiseUpdate において、Expression で受けることでプロパティ名を動的に取得している。

この部分を C#5.0 の CallerInfo を使う形で書き換えてみよう。

Caller Info 属性の一つである [CallerMemberName] を付けておくと、呼び出し元のメンバー名 (この場合はプロパティ名) がコンパイラーによって渡されてくる。
これを利用してみよう。

・フレームワーク部 - Observable (今回)
// C# による Oberver パターンの実装 その5
using System;
using System.Runtime.CompilerServices; // ☆ [CallerMemberName] の為に必要

abstract class Observable // 更新を監視される側
{
    public event Action<string> Update;

    protected void RaiseUpdate([CallerMemberName] string propertyName = "") // ☆ Caller Info 属性を利用
    {
        if (Update != null)
            Update(propertyName);
    }
}

すると、Observable の派生クラスで、プロパティ名を渡さなくて良くなる。

前回の Employee はこうだった。

・アプリケーション部 - Employee と EmployeeView (前回)
// C# による Oberver パターンの実装 その4
using System;

// アプリケーション部

// Model
class Employee : Observable
{
    int number = 0;
    string name = string.Empty;

    public int Number
    {
        get { return number; }
        set {
            if (value != number) {
                number = value;
                RaiseUpdate(() => Number); // ☆ RaiseUpdate がタイプセーフに
            }
        }
    }

    public string Name
    {
        get { return name; }
        set {
            if (value != name) {
                name = value;
                RaiseUpdate(() => Name); // ☆ RaiseUpdate がタイプセーフに
            }
        }
    }
}

これがこうなる。

・アプリケーション部 - Employee と EmployeeView (今回)
// C# による Oberver パターンの実装 その5
using System;

// アプリケーション部

// Model
class Employee : Observable
{
    int number = 0;
    string name = string.Empty;

    public int Number
    {
        get { return number; }
        set {
            if (value != number) {
                number = value;
                RaiseUpdate(); // ☆ RaiseUpdate の中で文字列でプロパティを渡す必要がなくなった
            }
        }
    }

    public string Name
    {
        get { return name; }
        set {
            if (value != name) {
                name = value;
                RaiseUpdate(); // ☆ RaiseUpdate の中で文字列でプロパティを渡す必要がなくなった
            }
        }
    }
}

前回のような動的にプロパティ名を取得する方法と異なり、コンパイラーが渡してくれるので、オーバーヘッドも小さくなる筈だ。

・全体のソースコード (今回)

全体のソースコードはこうなった。

// C# による Oberver パターンの実装 その5
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Runtime.CompilerServices; // ☆ [CallerMemberName] の為に必要

// フレームワーク部

public static class ObjectExtensions
{
    // オブジェクトの指定された名前のプロパティの値を取得
    public static object Eval(this object item, string propertyName)
    {
        var propertyInfo = item.GetType().GetProperty(propertyName);
        return propertyInfo == null ? null : propertyInfo.GetValue(item, null);
    }

    // Expression からメンバー名を取得
    public static string GetMemberName<ObjectType, MemberType>(this ObjectType @this, Expression<Func<ObjectType, MemberType>> expression)
    {
        return ((MemberExpression)expression.Body).Member.Name;
    }
}

abstract class Observable // 更新を監視される側
{
    public event Action<string> Update;

    protected void RaiseUpdate([CallerMemberName] string propertyName = "") // ☆ Caller Info 属性を利用
    {
        if (Update != null)
            Update(propertyName);
    }
}

abstract class Observer<ObservableType> where ObservableType : Observable // 更新を監視する側
{
    Dictionary<string, Action<object>> updateExpressions = new Dictionary<string, Action<object>>();
    ObservableType dataSource = null;

    public ObservableType DataSource
    {
        set {
            dataSource = value;
            value.Update += Update;
        }
    }
    
    protected void AddUpdateAction<PropertyType>(Expression<Func<ObservableType, PropertyType>> propertyExpression, Action<object> updateAction)
    {
        AddUpdateAction(dataSource.GetMemberName(propertyExpression), updateAction);
    }

    void AddUpdateAction(string propertyName, Action<object> updateAction)
    {
        updateExpressions[propertyName] = updateAction;
    }

    void Update(string propertyName)
    {
        Action<object> updateAction;
        if (updateExpressions.TryGetValue(propertyName, out updateAction))
            updateAction(dataSource.Eval(propertyName));
    }
}
// C# による Oberver パターンの実装 その5
using System;

// アプリケーション部

// Model
class Employee : Observable
{
    int number = 0;
    string name = string.Empty;

    public int Number
    {
        get { return number; }
        set {
            if (value != number) {
                number = value;
                RaiseUpdate(); // ☆ RaiseUpdate の中で文字列でプロパティを渡す必要がなくなった
            }
        }
    }

    public string Name
    {
        get { return name; }
        set {
            if (value != name) {
                name = value;
                RaiseUpdate(); // ☆ RaiseUpdate の中で文字列でプロパティを渡す必要がなくなった
            }
        }
    }
}

class TextControl // テキスト表示用のUI部品 (ダミー)
{
    public string Text
    {
        set { Console.WriteLine("TextControl is updated: {0}", value); }
    }
}

class EmployeeView : Observer<Employee> // Employee 用の View
{
    TextControl numberTextControl = new TextControl(); // Number 表示用
    TextControl nameTextControl = new TextControl(); // Name 表示用

    public EmployeeView()
    {
        AddUpdateAction(employee => employee.Number, number => numberTextControl.Text = ((int)number).ToString());
        AddUpdateAction(employee => employee.Name, name => nameTextControl.Text = (string)name);
    }
}

class Program
{
    static void Main()
    {
        var employee = new Employee(); // Model, Observable
        var employeeView = new EmployeeView(); // View, Observer

        employeeView.DataSource = employee; // データバインド
        employee.Number = 100; // Number を変更。employeeView に反映されたかな?
        employee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
    }
}
・実行結果

勿論、実行結果に変わりはない。

TextControl is updated: 100
TextControl is updated: 福井太郎

■ 今回のまとめと次回予告

今回は、前回のものに対して補足を行った。

Caller Info を使うことで、アプリケーション部でプロパティを指定する箇所が更にシンプルになった。

次回は、DynamicObject を使った全然別のアプローチを試してみたい。

お楽しみに。

2012年12月23日

[Windows 8] Logicool Rechargeable Touchpad T650 の紹介

Logicool Wireless Rechargeable Touchpad T650 のモニターに参加したので、報告してみたい。

■ Touchpad T650 とは

Touchpad T650 は、Windows 7 及び Windows 8 対応のタッチパッドだ。

先ずは外観から。

T650 の箱
T650 はこのような箱に入って届けられた。

T650 の見た目
T650 の見た目はいたってシンプル。

T650 の裏
T650 の裏。下の二つのゴムはマウスの左右ボタンになっている。

T650 の充電用 USB ケーブル
T650 を充電する為の USB ケーブルが付属している。

T650 を横から見たところ
T650 を横から見たところ。かなり薄い。

T650 をノート PC の横に置いたところ
T650 をノート PC の横に置いたところ。左側に置く人も居るかも知れない。

T650 を Windows ストア アプリ開発に使っているときの様子
私が T650 を Windows ストア アプリ開発に使っているときの様子。

■ T650 の特長

・ワイヤレスで充電式

T650 は、ワイヤレスで充電式だ。
充電も簡単で長持ちするし、使用時には余計なコードがない。
この辺、かなり使い勝手が良い。

  • ワイヤレス
    • ロジクール独自のワイヤレス技術
  • 充電式
    • リチウムポリマー電池内蔵
    • フル充電で一箇月持つ
    • USB ケーブルで充電
・Windows 8 のジェスチャに対応

Windows 8 は、それ迄の Windows と比べ、一層タッチ インタフェイスへの対応を強めている。

従来のようにマウスとキーボードで使える他、マイクロソフト デザイン (一時 Metro デザイン呼ばれていたもの) と云う新しいデザインの UI は、タッチでの使用に適している。

T650 は、マウスのように使える他、この Windows 8 の新しいタッチ インタフェイスに対応している。

次のような Windows 8 のジェスチャを行うことが出来るのだ。

Windows 8 のジェスチャ - Logicool

■ SetPoint による設定

T650 は、Logicool SetPoint というソフトウェアを PC にインストールすることで、様様な機能をカスタマイズして便利に使えるようになっている。

この SetPoint の設定画面を見ていくことで、T650 にどのような機能があるかが判るようになっている。
前述した Windows 8 のジェスチャについてもどのように対応しているか判るだろう。

順番に見ていこう。

SetPoint によるクリックの設定
SetPoint によるクリックの設定。

マウスのようなクリック操作の設定はここで行う。


SetPoint による Windows の操作の設定
SetPoint で様様なジェスチャーを Windows 8 の各操作に割り当てることができる。

Windows 8 の新しい UI であるスタート画面とデスクトップ画面の切り替えやチャーム、アプリ バー、スナップ等を設定することが出来る。


SetPoint によるスクロール等の操作の設定
SetPoint によるスクロール等の操作の設定。

スワイプやピンチによって、スクロールや、ズーム、進む、戻る等を行うことが出来る。

SetPoint は、動画を見ながら判り易く設定することが出来る。

その実際の様子は、次の動画で見ることができる。

・Logicool Rechargeable Touchpad T650 SetPoint の動画 (音声なし)

■ マウスやタッチ スクリーンとの比較

実際に使ってみて感じた、マウスやタッチ スクリーンと比べた良い点・悪い点を述べる。

マウスとの比較
  • 良い点
    • マウスでは出来ないジェスチャが可能。
      Windows 8 の新しい UI をタッチスクリーンが無くても使うことが出来る。
  • 悪い点
    • マウスに慣れた人には、マウス程細やかなマウスカーソルの移動はしづらい気がする。
タッチスクリーンと比較
  • 良い点
    • スクリーンをタッチする訳ではないので、指によって画面が隠れることがない。
    • スクリーンに指紋が付かない。
    • タッチパッドの表面がとても滑らかで操作しやすい。
  • 悪い点
    • タッチする場合、タッチ スクリーンは直接タッチするポイントに指を持っていくだけだが、タッチパッドでは、スクリーン自体は触れない訳なので、マウス カーソルを移動してのタッチとなる。
      この点は、マウスのような使い勝手とならざるを得ない。
      その為、直感性に劣る。

■ まとめ

T650 は、マウスとタッチスクリーンの間にある製品だ。

慣れるに従い、かなり使いやすく感じてきている。

表面がガラスでとても滑らかなので、スクロール操作やズーム操作、アプリケーションの切り替えが心地良い。

タッチ スクリーンのない PC で Windows 8 を使いたい場合、マウスだけで使うのは勿体ない。

そんな時、T650 はとても良い選択肢ではないだろうか。


2012年12月24日

[C#][dynamic] プラグイン処理 2 (DLL/C#/Python に対応させてみる)

Dynamic

前回の「プラグイン処理」の続き。

今回は、前回のコードに少し付け足して、様々な種類のプラグインに対応してみよう。

前回は、DLL だけをプラグインとして使えるようにしたが、今回は、それに加えて、C# と Python のプラグインも使えるようにしてみたい。

■ 今回のプラグインの規約

今回のプラグインも、前回同様、以下のような規約ベースで動くものとする。

  • 実行中のプログラムがあるフォルダーの下の Plugin と云う名前のフォルダーにある dll ファイルをプラグインのアセンブリ、cs ファイルを C# のプラグイン、py ファイルを Python のプラグインと看做す。
  • DLL プラグインと C# プラグインでは、最初の public なクラスをプラグインと見做す。
  • プラグインは、必ず string Name() と云う名称を返す public なメソッドを持っている。
  • プラグインは、必ず void Run() と云う public なメソッドによって動作する。

■ プラグイン側の実装の例

では、プラグイン側から実装してみよう。

今回用意するのは、以下の三種類だ。

・DLL プラグインの実装の例

DLL プラグインは、クラスライブラリとして作成し、ビルドして "Test.dll" とする。

public なクラスを持ち、その中には、public な Name() メソッドと public な Run() メソッドを持つ。

// DLL プラグイン (クラスライブラリとして作成し、ビルドして "Test.dll" に)
using System;

public class Plugin
{
    public string Name()
    {
        return "DLL Plugin";
    }

    public void Run()
    {
        Console.WriteLine("DLL Plugin is running!");
    }
}
・C# プラグインの実装の例

C# プラグインは、一つの cs ファイルだ。"Test.cs" として保存する。

public なクラスを持ち、その中には、public な Name() メソッドと public な Run() メソッドを持つ。

// C# プラグイン ("Test.cs" として保存)
using System;

public class Plugin
{
    public string Name()
    {
        return "C# Plugin";
    }

    public void Run()
    {
        Console.WriteLine("C# Plugin is running!");
    }
}
・Python プラグインの実装の例

Python プラグインは、一つの py ファイルだ。"Test.py" として保存する。

Name() メソッドと Run() メソッドを持つ。

# Python Plugnin (Save as "Test.py".)
def Name():
  return "Python plugin"

def Run():
  print "Python plugin is running!\n"

■ プラグインが組み込まれる本体側の実装の例

次に、プラグインが組み込まれる本体側の実装だ。

・IronPython を利用する為の準備

先ず、Python をプラグインとして使えるようにするために、IronPython をインストールしよう。

IronPython は、Visual Studio で NuGet からインストール出来る。

IronPython のインストール
IronPython のインストール

IronPython のインストールが終わると、プロジェクトの参照設定は、次のように IronPython を使う為の参照が追加されている。

IronPython のインストール後の参照設定
IronPython のインストール後の参照設定
・プラグインが組み込まれる本体側の実装の例

では、本体側を実装しよう。

using IronPython.Hosting; // Python プラグインの処理に必要
using Microsoft.CSharp; // C# プラグインの処理に必要
using System;
using System.CodeDom.Compiler; // C# プラグインの処理に必要
using System.IO;
using System.Linq;
using System.Reflection;

class Program
{
    // プラグインのフォルダー名
    const string pluginFolderName = "Plugin";
    
    static void Main()
    {
        // プラグインのフォルダーへのフルパス名を取得し、
        var pluginFolderName = GetPluginFolderName();
        // もしプラグインのフォルダーが在ったら、
        if (Directory.Exists(pluginFolderName))
            // プラグインのフォルダーの各のプラグインのパス名を使って、プラグインを実行
            Directory.GetFiles(pluginFolderName, "*.*").ToList().ForEach(Run);
    }

    // プラグインのフォルダーへのフルパス名を取得
    static string GetPluginFolderName()
    {
        // 実行中のこのプログラム自体のアセンブリのパスのフォルダーの下の Plugin フォルダーへのフルパスを取得
        return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + Path.DirectorySeparatorChar + pluginFolderName;
    }

    // プラグインを実行
    static void Run(string pluginPath)
    {
        switch (Path.GetExtension(pluginPath).ToLower()) {
            case ".dll": /* DLL の場合       */ RunDll(pluginPath); break;
            case ".cs" : /* C# のコードの場合 */ RunCSharp(pluginPath); break;
            case ".py": /* Python のコードの場合 */ RunPython(pluginPath); break;
        }
    }

    // DLL プラグインを実行
    static void RunDll(string path)
    {
        // DLL をアセンブリとして読み込む
        var assembly = Assembly.LoadFrom(path);
        if (assembly != null)
            // アセンブリをプラグインとして実行
            Run(assembly);
    }

    // C# プラグインを実行
    static void RunCSharp(string pathName)
    {
        // C# のコードをアセンブリに変換
        var assembly = CodeToAssembly(pathName);
        if (assembly != null)
            // アセンブリに変換されたプラグインを実行
            Run(assembly);
    }

    // Python プラグインを実行
    static void RunPython(string pathName)
    {
        dynamic plugin = Python.CreateRuntime().UseFile(pathName); // Python のコードからランタイムを作成し、
        Run(plugin); // それを実行
    }

    // プラグインを実行
    static void Run(Assembly pluginAssembly)
    {
        // アセンブリを読み込み、その中から public な最初のクラスを取り出す
        var pluginType = pluginAssembly.GetExportedTypes().FirstOrDefault(type => type.IsClass);
        if (pluginType != null)
            // インスタンスを生成し、それをプラグインとして実行
            Run(Activator.CreateInstance(pluginType));
    }

    // プラグインを実行
    static void Run(dynamic plugin)
    {
        Console.WriteLine(plugin.Name()); // プラグインの Name を表示し、
        plugin.Run(); // プラグインを Run
    }

    // C# のコードをコンパイルしてアセンブリに変換
    public static Assembly CodeToAssembly(string csharpCode)
    {
        using (var cscp = new CSharpCodeProvider()) {
            // コンパイルした結果のアセンブリを返す
            return cscp.CompileAssemblyFromFile(new CompilerParameters { GenerateInMemory = true }, csharpCode).CompiledAssembly;
        }
    }
}
  • DLL プラグインは前回と同じ。
    アセンブリとして読み込んで、その中から public な最初のクラスを取り出し、中のメソッドを dynamic に実行する。
  • C# プラグインはコンパイルし、メモリ上でアセンブリに変換する。後は DLL プラグインと同じ。
  • Python プラグインは、コンパイルせずに、動的にスクリプトとして実行することでメソッドを呼び出す。

dynamic を使うことで、Python のプラグインも同じように実行することができる。

■ プラグイン処理の実行例

では、実行してみよう。

プログラムを実行しているフォルダーの下に Plugin と云う名前のフォルダーを作成し、そこに三つのプラグイン "Test.dll"、"Test.cs"、"Test.py" を置く。

Plugin フォルダーの中に置かれた三つのプラグイン
Plugin フォルダーの中に置かれた三つのプラグイン

本体プログラムの実行結果は次の通りだ。

DLL Plugin
DLL Plugin is running!
C# Plugin
C# Plugin is running!
Python plugin
Python plugin is running!

各プラグインが実行された。

■ 今回のまとめ

今回は、前回のプラグイン処理に少し補足を行った。

Visual Basic.NET や F#、IronRuby 等も同様に扱えるのでないだろうか。

2012年12月25日

[C#][Design Pattern][DynamicObject][dynamic] C# による Observer パターンの実装 その6 - DynamicObject を使ってオブザーバーを作る

C# Advent Calender 2012 の 25日目のエントリー。
Observer

本ブログでは、これ迄五回に渡り、C# による Observer パターンの実装をご紹介してきた。

前前回の「C# による Observer パターンの実装 その4 - Expression を使ってプロパティの指定をタイプセーフに」と前回の「C# による Observer パターンの実装 その5 - Caller Info を使ってプロパティの指定をよりシンプルに」と云う記事では、Expression や Caller Info を用いることで、第三回で文字列でプロパティを指定していた部分をシンプルな記述にしてみた。

今回は、同じく第三回からの改良を行ってみたい。
第四回第五回の遣り方とは別のアプローチでアプリケーション部をシンプルにしてみたい。

■ DynamicObject の応用

以前、「DynamicObject を使ってみよう」や「DynamicObject を使ってみよう その 2」と云う記事で DynamicObject をご紹介した。

これらの記事で、DynamicObject を用いることで、プロパティの設定時の処理を定義出来ることを示した。

今回は、この仕組みを用いて、フレームワーク部の Observable で動的にプロパティの更新を捕まえ、更新イベントを発行してみよう。

■ C# での Observer パターンの実装 5

・クラス図

全体像をざっと把握していただくために、先ずクラス図を示そう。

「C# による Observer パターンの実装 その6」のクラス図
「C# による Observer パターンの実装 その6」のクラス図

引数と戻り値の型を書き加えたクラス図だとこうなる。

「C# による Observer パターンの実装 その6」のクラス図 (引数と戻り値の型を書き加えたもの)
「C# による Observer パターンの実装 その6」のクラス図 (引数と戻り値の型を書き加えたもの)

例によって、青い部分がフレームワーク部、赤い部分がアプリケーション部だ。

・フレームワーク部の実装

では、フレームワーク部から実装していこう。

・フレームワーク部の実装 - ObjectExtensions

第三回の ObjectExtensions を少し書き換えて、今回の DynamicObject を用いた例に対応できるようにする。

using System.Dynamic;

// フレームワーク部

public static class ObjectExtensions
{
    // オブジェクトの指定された名前のプロパティの値を取得
    public static object Eval(this object item, string propertyName, out bool isSucceeded)
    {
        var propertyInfo = item.GetType().GetProperty(propertyName);
        if (propertyInfo == null) {
            isSucceeded = false;
            return null;
        }
        isSucceeded = true;
        return propertyInfo.GetValue(item, null);
    }

    // オブジェクトの指定された名前のプロパティの値を設定
    public static void SetPropertyValue(this object item, string propertyName, object value)
    {
        var propertyInfo = item.GetType().GetProperty(propertyName);
        if (propertyInfo != null)
            propertyInfo.SetValue(item, value, null);
    }

    // DynamicObject の指定された名前のプロパティの値を取得
    public static object GetPropertyValue(this DynamicObject item, string propertyName)
    {
        object result;
        return item.TryGetMember(new MyGetMemberBinder(propertyName), out result) ? result : null;
    }
    
    // GetPropertyValue 用
    class MyGetMemberBinder : GetMemberBinder
    {
        public MyGetMemberBinder(string name) : base(name, false)
        { }

        public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
        { return null; }
    }
}

object に指定された名前のプロパティの値を設定する拡張メソッドや、DynamicObject から指定された名前のプロパティの値を取得する拡張メソッドを追加した。

・フレームワーク部の実装 - DynamicContainer

DynamicContainer は、「DynamicObject を使ってみよう」に出てきたのと同様、DynamicObject の派生クラスだ。

あの時と同じように、TrySetMember と TryGetMember をオーバーライドする。

あの時との違いは、target としてのオブジェクトを持ち、値を設定したり取得したりする際はそちらに行うことだ。

従って、Dictionary の中には値を保持する必要がない。

最初に target を受け取った際に、その target の全プロパティの型情報のみ格納しておく
(本当は、各プロパティが public な set と public な get の両方を持つかどうかの情報も保持した方が良いが、今回は省略)。

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;

// フレームワーク部

// 任意のオブジェクトを格納してプロパティの値の変化を監視
class DynamicContainer : DynamicObject
{
    // 対象とするアイテム
    object target;
    // observableTarget の該当するプロパティ名毎に型を格納
    Dictionary<string, Type> types = new Dictionary<string, Type>();

    public DynamicContainer(object target)
    {
        this.target = target;
        SetProperties(target.GetType()); // 対象とする型の全プロパティを格納
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        Type type;
        // 該当するプロパティの型を values から取得
        if (types.TryGetValue(binder.Name, out type)) {
            var valueType = value.GetType();
            // もしプロパティに設定しようとしている値の型が、そのプロパティの型もしくはそのサブクラスだったら
            if (valueType.Equals(type) || valueType.IsSubclassOf(type)) {
                // target のプロパティに値を設定
                target.SetPropertyValue(binder.Name, value);
                return true;
            }
        }
        return false;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        // target のプロパティから値を取得
        bool isSucceeded;
        result = target.Eval(binder.Name, out isSucceeded);
        return isSucceeded;
    }

    // 対象とする型の全プロパティを格納
    void SetProperties(Type type)
    {
        type.GetProperties().ToList().ForEach(SetProperty);
    }

    // 対象とするアイテムのプロパティを格納
    void SetProperty(PropertyInfo propertyInfo)
    {
        types[propertyInfo.Name] = propertyInfo.PropertyType;
    }
}
・フレームワーク部の実装 - DynamicObservable

次に、DynamicContainer を継承して DynamicObservable を作る。

前回迄の Observable にあたるクラスだ。

Observable と異なり、抽象クラスではない。

これまでは、イベントを起こす為のメソッド RaiseUpdate をアプリケーション側のモデルで、変更されるプロパティ毎に呼ぶ必要があった。

この DynamicObservable では、TrySetMember をオーバーライドし、もし値が更新された場合は、自ら Update イベントを起こす。

これにより、アプリケーション部のモデルがシンプルになる筈だ。

using System;
using System.Dynamic;

// フレームワーク部

class DynamicObservable : DynamicContainer // 更新を監視される側
{
    public event Action<string> Update;

    public DynamicObservable(object target) : base(target)
    { }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        object oldValue = this.GetPropertyValue(binder.Name);
        if (base.TrySetMember(binder, value)) {
            if (!value.Equals(oldValue))
                RaiseUpdate(binder.Name);
            return true;
        }
        return false;
    }

    void RaiseUpdate(string propertyName)
    {
        if (Update != null)
            Update(propertyName);
    }
}
・フレームワーク部の実装 - DynamicObserver

フレームワーク部の実装の最後は、DynamicObserver だ。

前回迄の Observer にあたるクラスだ。

このクラスは、DataSource が Observable から DynamicObservable に変化した以外は、変更はない。

using System;
using System.Collections.Generic;

// フレームワーク部

abstract class DynamicObserver // 更新を監視する側
{
    Dictionary<string, Action<object>> updateExpressions = new Dictionary<string, Action<object>>();
    DynamicObservable dataSource = null;

    public DynamicObservable DataSource
    {
        set {
            dataSource = value;
            value.Update += Update;
        }
    }

    protected void AddUpdateAction(string propertyName, Action<object> updateAction)
    {
        updateExpressions[propertyName] = updateAction;
    }

    void Update(string propertyName)
    {
        Action<object> updateAction;
        if (updateExpressions.TryGetValue(propertyName, out updateAction))
            updateAction(dataSource.GetPropertyValue(propertyName));
    }
}
・アプリケーション部の実装

次に、アプリケーション部だ。
どんな風にシンプルになっただろう。

・アプリケーション部の実装 - Employee

Employee は、こうなる。

プロパティが設定される毎に Update イベントを起こさなくて良くなったため、随分シンプルだ。

特定のクラスからの派生が不要な POCO (Plain Old CLR Object) になった。

// アプリケーション部

// Model
class Employee
{
    public int Number { get; set; }
    public string Name { get; set; }
}
・アプリケーション部の実装 - EmployeeView

EmployeeView は、こうだ。

第三回から全く変化していない。

using System;

// アプリケーション部

class TextControl // テキスト表示用のUI部品 (ダミー)
{
    string text = string.Empty;

    public string Text
    {
        set { text = value; Console.WriteLine("TextControl is updated: {0}", value); }
        get { return text; }
    }
}

class EmployeeView : DynamicObserver // Employee 用の View
{
    TextControl numberTextControl = new TextControl(); // Number 表示用
    TextControl nameTextControl = new TextControl(); // Name 表示用

    public EmployeeView()
    {
        AddUpdateAction("Number", number => numberTextControl.Text = number.ToString());
        AddUpdateAction("Name", name => nameTextControl.Text = (string)name);
    }
}
・アプリケーション部の実装 - Main を含んだクラス Program

Main を含んだクラス Program では、少し変更がある。

EmployeeView のデータソースに DynamicObservable を用いるようにする。

using System;

// アプリケーション部

class Program
{
    static void Main()
    {
        var employee = new Employee(); // Model
        dynamic observableEmployee = new DynamicObservable(employee); // Observable
        var employeeView = new EmployeeView(); // View, Observer

        employeeView.DataSource = observableEmployee; // データバインド
        observableEmployee.Number = 100; // Number を変更。employeeView に反映されたかな?
        observableEmployee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
    }
}
・実行結果

勿論、実行結果に変わりはない。

TextControl is updated: 100
TextControl is updated: 福井太郎

矢張り、Number と Name それぞれの更新で、それぞれの TextControl が更新されている。

今回は、もう少し念入りにチェックしてみよう。

using System;

// アプリケーション部

class Program
{
    static void Main()
    {
        var employee = new Employee(); // Model
        dynamic observableEmployee = new DynamicObservable(employee); // Observable
        var employeeView = new EmployeeView(); // View, Observer

        employeeView.DataSource = observableEmployee; // データバインド

        observableEmployee.Number = 100; // Number を変更。employeeView に反映されたかな?
        observableEmployee.Number = 200; // Number を再度変更。employeeView に反映されたかな?

        observableEmployee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
        observableEmployee.Name = "山田次郎"; // Name を再度変更。employeeView に反映されたかな?

        observableEmployee.Number = 200; // Number を変更していない。employeeView は更新されない筈
        observableEmployee.Name = "山田次郎"; // Name を変更していない。employeeView は更新されない筈

        // 念の為値を確認
        Console.WriteLine("employee - Number:{0}, Name:{1}", employee.Number, employee.Name);
        Console.WriteLine("observableEmployee - Number:{0}, Name:{1}", observableEmployee.Number, observableEmployee.Name);
    }
}
・実行結果

実行結果は予想通り。
この範囲ではうまく行っているようだ。

TextControl is updated: 100
TextControl is updated: 200
TextControl is updated: 福井太郎
TextControl is updated: 山田次郎
employee - Number:200, Name:山田次郎
observableEmployee - Number:200, Name:山田次郎

■ 今回のまとめ

今回は、DynamicObject を使ったアプローチを行った。

モデル部分がシンプルに書けるようになった。

2012年12月27日

[Event][Windows 8][Windows ストア アプリ] VSUG DAY 2012 Winter 「Windows 8 ストア アプリ 開発 Tips」の資料公開

VSUG DAY 2012 Winter 2012.12.15(Sat) マイクロソフト品川ビル31F

2012/12/15(土) VSUG DAY 2012 Winter の時の資料を公開します。

■ 関連リンク

2012年12月28日

[Event][Windows 8][Windows ストア アプリ] 「Community Open Day 2012 北陸」 (6月9日) での「JavaScript+HTML5 と C#+XAML で作る Windows8 アプリ」資料公開

大分前のものだが、「Community Open Day 2012 北陸」 (6月9日, 石川工業高等専門学校) での「JavaScript+HTML5 と C#+XAML で作る Windows8 アプリ」も公開。

■ 関連リンク

2012年12月29日

[Event] Developers Summit 2012 の LT「10年後も世界で通じるエンジニアであるために」の資料公開

大分前のものだが、Developers Summit 2012 (2月16-17日) の「デブサミオフィシャルコミュニティから選出のLT大会2012」でやったライトニングトークスの資料も公開。

■ 関連リンク

About 2012年12月

2012年12月にブログ「プログラミング C# - 翔ソフトウェア (Sho's)」に投稿されたすべてのエントリーです。過去のものから新しいものへ順番に並んでいます。

前のアーカイブは2012年11月です。

次のアーカイブは2013年01月です。

他にも多くのエントリーがあります。メインページアーカイブページも見てください。

Powered by
Movable Type 3.35