プログラミング C# - 翔ソフトウェア (Sho's)

C#/.NET/ソフトウェア開発など

DynamicObject を使ってみよう その 2

Dynamic

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

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

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

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

■ DynamicObject を使った例

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

例えば、ASP.NETASP.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>*1;
    }
}

実行結果は次の通り。

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>*2;

        // 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>*3;

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

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

        // 2. 次に、Update にイベントハンドラーっぽいものを追加
        item.Update += new Action<dynamic, EventArgs>*4;

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

*1:o, value) => { o.Id = value; }); item.GetId = new Func<dynamic, int>(o => o.Id); // 呼んでみる item.SetId(item, 30); Console.WriteLine(item.GetId(item

*2:sender, _) => Console.WriteLine(sender.Name

*3:o, value) => { o.Id = value; }); item.GetId = new Func<dynamic, int>(o => o.Id); // 呼んでみる item.SetId(item, 30); Console.WriteLine(item.GetId(item

*4:sender, _) => Console.WriteLine(sender.Name