[C# DynamicObject] 動的にプロパティを設定する

[C# 入門] 動的型付け変数(dynamic型)についてでは動的な型について書きましたが、動的にオブジェクトのメンバー(プロパティ)を設定したいというときに使えるDynamicObjectクラスを紹介します。

DynamicObjectクラスは名前空間System.Dynamicにあります。
DynamicObjectクラスは継承してメンバーの取得・設定用メソッドを上書きして使います。

プロパティの設定・取得時にそんなに複雑なことはしない場合、ExpandoObjectクラスを使うと便利です。こちらはクラスを継承せずに使うことができます。

スポンサーリンク

プロパティを動的に設定して使う

プロパティを動的に設定するには、DynamicObjectクラスのTrySetMemberメソッドTryGetMemberメソッドを上書きします。

TrySetMemberメソッド外部からプロパティの値を設定しようとした時に呼ばれるメソッドです。第1引数SetMemberBinder型変数binderから外部から指定されたプロパティ名を取得することができ、第2引数objectには外部から設定された値が入っています。戻り値は操作が正常に終わった場合にTrue、それ以外の場合にFalseを返すようにします。

TryGetMemberメソッド外部からプロパティの値を取得しようとした時に呼ばれるメソッドです。第1引数SetMemberBinder型変数binderから外部から指定されたプロパティ名を取得することができ、第2引数はoutが付いていて、objectにメソッド内で指定されたプロパティの値を設定するようにします。
戻り値は操作が正常に終わった場合にTrue、それ以外の場合にFalseを返すようにします。

2つのメソッドともに戻り値にFalseを返すとエラーになります。

using System;
using System.Collections.Generic;
using System.Dynamic;
// メインプログラム
class Program
{
    public static void Main()
    {
        // dynamic型の変数に動的オブジェクトを作成して入れる
        dynamic dy = new DynamicClass();

        // 動的にプロパティを設定
        dy.Prop1 = 10;
        dy.Prop2 = "abc";
        dy.Prop3 = new List<int> { 1, 2, 3 };

        // 動的に設定したプロパティを取得
        Console.WriteLine($"Prop1={dy.Prop1}");
        Console.WriteLine($"Prop2={dy.Prop2}");
        Console.WriteLine($"Prop3={string.Join(", ", dy.Prop3)}");

        // 継承したクラスに定義済のプロパティの場合は
        // TrySetMember、TryGetMemberは呼ばれない
        dy.PropX = 100;
        Console.WriteLine($"PropX={dy.PropX}");
    }
}

// DynamicObjectを継承してプロパティ設定/取得用のメソッドを上書き
class DynamicClass : DynamicObject
{
    // 値保持用のDictionary
    private Dictionary<string, dynamic> dicProperties = new Dictionary<string, dynamic>();

    // 外部からプロパティの値を設定しようとした時に呼ばれるメソッド
    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        // binder.Nameにプロパティ名が入っている
        this.dicProperties[binder.Name] = value;
        return true;
    }

    // 外部からプロパティの値を取得しようとした時に呼ばれるメソッド
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        // resultに値を格納して返す
        return this.dicProperties.TryGetValue(binder.Name, out result);
    }

    // DynamicClass独自のプロパティを定義
    public int PropX { get; set; }
}

処理結果はこんな感じです。

Prop1=10
Prop2=abc
Prop3=1, 2, 3
PropX=100

サンプルではDynamicObjectを継承したDynamicClassの内部に値の保存用Dictionaryを定義して、プロパティが設定されるとそこにプロパティ名をキーにして値を保存、プロパティが呼び出されるとそこから値を取り出して返しています。

定義済のプロパティの場合は、TrySetMemberメソッド、TryGetMemberメソッドは呼ばれません。

スポンサーリンク

インデクサーを使ってさらに動的に設定する

上のサンプルではコードを書くときにプロパティ名が分かっている必要がありましたが、インデクサーを使ってプロパティ名と値を設定するようにすればプログラム実行時までにプロパティ名が判明すれば大丈夫になります。

using System;
using System.Collections.Generic;
using System.Dynamic;
// メインプログラム
class Program
{
    public static void Main()
    {
        // dynamic型の変数に動的オブジェクトを作成して入れる
        dynamic dy = new DynamicClass();

        // 動的にプロパティを設定
        // こっちはコードを書くときにプロパティ名が判明している必要がある
        dy.Prop1 = 10;
        dy.Prop2 = "abc";

        // インデクサーを使ってプロパティの追加
        // こっちはプログラム実行時にプロパティ名が分かればOK
        Console.WriteLine("プロパティ名を入力してください。");
        var propertyName = Console.ReadLine();
        dy[propertyName] = new List<int> { 1, 2, 3 };

        // 動的に設定したプロパティを取得
        Console.WriteLine($"Prop1={dy.Prop1}");
        Console.WriteLine($"Prop2={dy.Prop2}");
        Console.WriteLine($"{propertyName}={string.Join(", ", dy[propertyName])}");
    }
}

// DynamicObjectを継承してプロパティ設定/取得用のメソッドを上書き
class DynamicClass : DynamicObject
{
    // 値保持用のディクショナリ
    private Dictionary<string, dynamic> dicProperties = new Dictionary<string, dynamic>();

    // 外部からプロパティの値を設定しようとした時に呼ばれるメソッド
    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        // binder.Nameにプロパティ名が入っている
        this.dicProperties[binder.Name] = value;
        return true;
    }

    // 外部からプロパティの値を取得しようとした時に呼ばれるメソッド
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        // resultに値を格納して返す
        return this.dicProperties.TryGetValue(binder.Name, out result);
    }

    // インデクサーでプロパティの設定ができるようにする
    public dynamic this[string name] {
        get => this.dicProperties[name];
        set => this.dicProperties[name] = value;
    }
}

処理結果はこんな感じです。

プロパティ名を入力してください。
Prop999
Prop1=10
Prop2=abc
Prop999=1, 2, 3

ExpandoObjectのようにDictionaryっぽくプロパティを追加しつつ、継承したクラス独自のメンバーも定義したいときにこの方法を使っています。


C# プログラミング講座に戻る

コメント

タイトルとURLをコピーしました