« 2013年05月 | メイン | 2013年07月 »

2013年06月 アーカイブ

2013年06月12日

[C#][dynamic] 列挙型 (enum) の列挙子の動的な取得など

今回は、列挙型 (enum) の列挙子の取得などについて。

■ 列挙型 (enum) の列挙子を動的なメソッド呼び出しの引数で渡したい

例えば、次のようなクラス ライブラリー (ClassLibrary.dll) があるとする。

// クラス ライブラリー側: ClassLibrary.dll
namespace ClassLibrary
{
    // 列挙型 (enum)
    public enum ItemKind
    {
        Book, Toy, Stationary
    }

    // クラス
    public class Item
    {
        // アイテムの種類 (型は列挙型の ItemKind)
        readonly ItemKind itemType;

        // コンストラクター (引数でアイテムの種類を指定)
        public Item(ItemKind itemType)
        { this.itemType = itemType; }
    }
}

そして、別のプロジェクトで、このクラス ライブラリーを動的に読み込んで使いたいとする。

例.

// クラス ライブラリーを動的に利用する側: Xxx.exe
using System;
using System.Reflection;

class Program
{
    static void Main()
    {
        // アセンブリ名を使ってクラス ライブラリーを動的に読み込み
        var assembly = Assembly.Load("ClassLibrary");
        // アセンブリ内のクラスの型情報を取得
        var itemType = assembly.GetType("ClassLibrary.Item");

        // ... 途中省略 ...
    }
}

ここで次のように、型情報 itemType を使って、インスタンスを生成させようとしてみる。

        // ... 途中省略 ...

        // アセンブリ名を使ってクラス ライブラリーを動的に読み込み
        var assembly = Assembly.Load("ClassLibrary");
        // アセンブリ内のクラスの型情報を取得
        var itemType = assembly.GetType("ClassLibrary.Item");

        // クラスの型情報を使って、クラスのインスタンスを生成
        dynamic item = Activator.CreateInstance(itemType, 0 /* ClassLibrary.ItemKind.Book の値 */);

        // ... 途中省略 ...

これは次のように、実行時エラーになる。
コンストラクターの引数の型が合っていないからだ。

実行時エラー その1

勿論、object にキャストしても実行時エラーの儘だ。

実行時エラー その2

だが、クラス ライブラリーを動的に読み込みたい訳なので、ClassLibrary.ItemKind.Book と書く訳にもいかない。

        // ... 途中省略 ...

        // クラスの型情報を使って、クラスのインスタンスを生成
        dynamic item = Activator.CreateInstance(itemType, ClassLibrary.ItemKind.Book); // コンパイル エラー

        // ... 途中省略 ...

こういう場合はどうすれば良いだろうか?

これは、次のように、列挙型 (enum) の型情報も取得して、そこから ClassLibrary.ItemKind.Book を生成してやれば良い。

        // ... 途中省略 ...

        // アセンブリ内の列挙型 (enum) の型情報をアセンブリから取得
        var itemKindType     = assembly.GetType("ClassLibrary.ItemKind");
        // enum の列挙オブジェクト ClassLibrary.ItemKind.Book を生成
        var itemKindTypeBook = Enum.Parse(itemKindType, "Book");

        // クラスの型情報を使って、クラスのインスタンスを生成
        dynamic item = Activator.CreateInstance(itemType, itemKindTypeBook);

        // ... 途中省略 ...

Enum.Parse メソッドを使って enum の列挙子名から列挙オブジェクトを生成するのがポイントだ。

■ 列挙型 (enum) の列挙子の名称を取得

列挙型 (enum) の型情報から全ての列挙子の名称を取得することもできる。

例えば、列挙型 (enum) ClassLibrary.ItemKind の全ての列挙子の名称を取得するには次のようにすれば良い。

        // ... 途中省略 ...

        // 列挙型 (enum) の全ての列挙子の名称を取得
        string[] itemKindTypeNames = Enum.GetNames(typeof(ClassLibrary.ItemKind));

        // ... 途中省略 ...

列挙子の名称が配列で得られる。即ち、ここから列挙子の数を取得することができることになる。

        // ... 途中省略 ...

        // 列挙型 (enum) の列挙子の数を取得
        int itemKindTypeLength = Enum.GetNames(typeof(ClassLibrary.ItemKind)).Length;

        // ... 途中省略 ...

Enum.GetTypes メソッドで、全ての列挙子オブジェクトを配列 (Array) で取得することもできるので、次のようにしても良い。

        // ... 途中省略 ...

        // 列挙型 (enum) の列挙子の数を取得
        int itemKindTypeLength = Enum.GetTypes(typeof(ClassLibrary.ItemKind)).Length;

        // ... 途中省略 ...

2013年06月13日

[C#][dynamic] 動的にイベント ハンドラーを追加

今回は、動的にイベント ハンドラーの追加を行ってみたい。

■ 動的に読み込まれるクラス ライブラリー側

例えば、次のようなクラス ライブラリー (ClassLibrary.dll) があるとする。

// クラス ライブラリー側: ClassLibrary.dll
namespace ClassLibrary
{
    public class Data : IEnumerable<string>
    {
        // 更新されると起きるイベント
        public event EventHandler Update;

        List<string> itemList = new List<string>();

        // アイテムの追加
        public void Add(string item)
        {
            itemList.Add(item);
            // 更新イベントを起こす
            if (Update != null)
                Update(this, null);
        }

        // IEnumerable<string> の実装
        public IEnumerator<string> GetEnumerator()
        { return itemList.GetEnumerator(); }

        // IEnumerable<string> の実装
        IEnumerator IEnumerable.GetEnumerator()
        { return GetEnumerator(); }
    }
}

クラス Data は、文字列のコンテナー クラスで、文字列が追加 (Add) されると、Update イベントが起きるようになっている。

■ クラス ライブラリーを動的に読み込む側

別のプロジェクトで、このクラス ライブラリーを動的に読み込んで使いたいとする。

こちらでは、先ず先の Data を表示するための View クラスを用意することにする。

・View クラス
using System;
using System.Collections.Generic;

class View
{
    public IEnumerable<string> DataSource { private get; set; }

    // Update のイベント ハンドラー用
    public void OnUpdate(object sender, EventArgs e)
    {
        Show();
    }

    // DataSource の内容を表示
    void Show()
    {
        Console.WriteLine("View:");
        if (DataSource == null)
            return;
        foreach (var item in DataSource)
            Console.WriteLine("\t{0}", item);
    }
}

表示したい IEnumerable<string> な DataSource を予め設定しておくと、OnUpdate が呼ばれたときにそれを表示する、というクラスだ。

・動的にイベント ハンドラーを設定

それでは、クラス ライブラリーを動的に読み込み、その中の Data クラスのインスタンスを生成し、それにイベント ハンドラーを追加してみよう。

クラス ライブラリーを動的に読み込み、その中の Data クラスのインスタンスを生成
using System;
using System.Collections.Generic;
using System.Reflection;

class Program
{
    static void Main()
    {
        // アセンブリ名を使ってクラス ライブラリーを動的に読み込み
        Assembly assembly = Assembly.Load("ClassLibrary");
        // アセンブリ内のクラス Data の型情報を取得
        Type     dataType = assembly.GetType("ClassLibrary.Data");
        // アセンブリ内のクラス Data のインスタンスを生成
        var      data     = Activator.CreateInstance(dataType);
    }
}
続いて、View のインスタンスを生成して DataSource に data を設定
// ...省略...
class Program
{
    static void Main()
    {
        // ...省略...

        // View のインスタンスを生成して DataSource に data を設定
        var view = new View { DataSource = data as IEnumerable<string> };
    }
}
次に、イベント ハンドラー view.OnUpdate からデリゲートを作成し、それを動的にイベント ハンドラーとして追加
// ...省略...
class Program
{
    static void Main()
    {
        // ...省略...

        // Delegate クラスを利用してイベント ハンドラーである view.OnUpdate からデリゲートを作成
        Delegate eventHandlerDelegate = Delegate.CreateDelegate(typeof(EventHandler), view, "OnUpdate");

        // アセンブリ内のクラスの Update イベントの EventInfo を取得
        EventInfo eventInfo = dataType.GetEvent("Update");
        // EventInfo に対してイベント ハンドラーを追加
        eventInfo.AddEventHandler(data, eventHandlerDelegate);
    }
}
・試してみる

では試してみよう。

data に対して、文字列を追加 (Add) する度に view.OnUpdate が呼ばれれば OK だ。

// ...省略...
class Program
{
    static void Main()
    {
        // ...省略...

        // data.Add("Apple"); を動的に呼び出す
        dataType.InvokeMember("Add", BindingFlags.InvokeMethod, null, data, new object[] { "Apple"  });
        // data.Add("Banana"); を動的に呼び出す
        dataType.InvokeMember("Add", BindingFlags.InvokeMethod, null, data, new object[] { "Banana" });
    }
}

結果コンソールには、次のように表示される。

View:
        Apple
View:
        Apple
        Banana

文字列を追加 (Add) する度にイベント ハンドラー view.OnUpdate が呼ばれているのが判る。

2013年06月14日

[C#][ラムダ式][式木] Expression でラムダ式を作成してデリゲートとして実行する

Expression

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

その中で、ラムダ式として足し算を行うだけの (x, y) => x + y と云うシンプルなラムダ式を Expression で受け、その構造を調べた。

using System;
using System.Linq.Expressions;

class Program
{
    static void Main()
    {
        Expression<Func<int, int, int>> expression = (x, y) => x + y;
    }
}

今回は、逆に Expression でラムダ式を作成し、これをデリゲートとして実行してみよう。

■ 今回のサンプルプログラム

今回のサンプルプログラムは次の通りだ。

using System;
using System.Linq.Expressions;

class Program
{
    static void Main()
    {
        // ラムダ式の Parameter の x
        ParameterExpression parameterX = Expression.Parameter(typeof(int), "x");
        // ラムダ式の Parameter の y
        ParameterExpression parameterY = Expression.Parameter(typeof(int), "y");

        // ラムダ式の Body: x + y
        BinaryExpression body = Expression.Add(parameterX, parameterY);

        // ラムダ式: Parameter の x と y、Body で作成
        Expression<Func<int, int, int>> lamda = Expression.Lambda<Func<int, int, int>>(body, parameterX, parameterY);

        // ラムダ式をコンパイルしてデリゲートを生成
        Func<int, int, int> lamdaDelegate = lamda.Compile();

        // 実行してみる: result が 3 になる
        int result = lamdaDelegate(1, 2);
    }
}

このように、Expression でラムダ式をツリー上に組立て、コンパイルすることで、これをデリゲートとして実行することができる。

About 2013年06月

2013年06月にブログ「プログラミング C# - 翔ソフトウェア (Sho's)」に投稿されたすべてのエントリーです。過去のものから新しいものへ順番に並んでいます。

前のアーカイブは2013年05月です。

次のアーカイブは2013年07月です。

他にも多くのエントリーがあります。メインページアーカイブページも見てください。

Powered by
Movable Type 3.35