リフレクションとは、Wikipediaによると
プログラムの実行過程でプログラム自身の構造を読み取ったり書き換えたりする技術のことである。
だそうです。
ということで、サーバーからJSONなどでパラメータを取得してクライアント側のモデルクラスを初期化するような場合にC#のリフレクション機能を使ってみたのでその紹介をします。
抽象クラスにリフレクションによりパラメータ初期化するメソッドを定義しておくだけで、サーバーからモデルクラスのField名かProperty名をキーとして渡すだけで動的に初期化をすることができます。
これをしないとParserから値を引き出す時に対応するキーを指定し、さらにコンストラクタにすべてのパラメータを代入する必要があるのでなかなか面倒です。
サーバーも実装するようなプロジェクトではできるだけ楽に要件追加できるといいですよね。
少し楽ができるTipsとして参考にしていただければと思います。
Android(Galaxy S2, 4.0.4)、iPhone5(iOS7.0)でも問題なく動作しました。
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
public class BaseModel <T> {
// privateのメンバ変数を初期化する時はこっち
public void SetField (string key, object data ) {
// 変数名をキーにprivateのメンバ変数を取得
FieldInfo fieldInfo = this.GetType().GetField (key, BindingFlags.NonPublic | BindingFlags.Instance);
// 型ごとにキャストして変数を初期化
if (fieldInfo.FieldType == typeof(int)) {
fieldInfo.SetValue(this, (int)data);
} else if (fieldInfo.FieldType == typeof(string)) {
fieldInfo.SetValue(this, (string)data);
} else if (fieldInfo.FieldType == typeof(double)) {
fieldInfo.SetValue(this, (double)data);
}
}
// プロパティを初期化する時はこっち
public void SetProperty (string key, object data ) {
// プロパティ名をキーにプロパティを取得
PropertyInfo propertyInfo = this.GetType().GetProperty (key, BindingFlags.Public | BindingFlags.Instance);
// 型ごとにキャストしてプロパティを初期化
if (propertyInfo.PropertyType == typeof(int)) {
propertyInfo.SetValue(this, (int)data, null);
} else if (propertyInfo.PropertyType == typeof(string)) {
propertyInfo.SetValue(this, (string)data, null);
} else if (propertyInfo.PropertyType == typeof(double)) {
propertyInfo.SetValue(this, (double)data, null);
}
}
}
// getterのみ用意されたSubクラス
public class ItemModel : BaseModel<ItemModel> {
private int id;
private string name;
private int price;
private static List<ItemModel> items;
// getterのみ用意する
public int Id { get { return this.id; } }
public string Name { get { return this.name; } }
public int Price { get { return this.price; } }
public static List<ItemModel> Items { get { return items; } }
// コンストラクタにはDctionaryを渡すだけでよく、変数などは気にする必要がない
public ItemModel (Dictionary<string, object> param) {
foreach (string key in param.Keys) {
SetField (key, param[key]);
}
}
// リフレクションを使わない場合は変数の初期化をすべて列挙する必要がある
// またParserから値を受け取る時にも対応するキーで引き出す必要がある
public ItemModel (int id, string name, int price) {
this.id = id;
this.name = name;
this.price = price;
}
// データを適切な形式でもらえればDictionaryにキャストして渡すだけでモデルを初期化できる
public static void Init (List<object> list) {
items = new List<ItemModel> ();
foreach (Dictionary<string, object> dict in list) {
ItemModel item = new ItemModel (dict);
items.Add (item);
}
}
}
// getter,setter共に用意されたSubクラス
public class FighterModel : BaseModel<FighterModel> {
private int id;
private string name;
private int power;
private double speed;
private static List<FighterModel> fighters;
public int Id { get; set; }
public string Name { get; set; }
public int Power { get; set; }
public double Speed { get; set; }
public static List<FighterModel> Fighters { get { return fighters; } }
// コンストラクタにはDctionaryを渡すだけでよく、変数などは気にする必要がない
public FighterModel (Dictionary<string, object> param) {
foreach (string key in param.Keys) {
SetProperty (key, param[key]);
}
}
// リフレクションを使わない場合は変数の初期化をすべて列挙する必要がある
// またParserから値を受け取る時にも対応するキーで引き出す必要がある
public FighterModel (int id, string name, int power, double speed) {
this.id = id;
this.name = name;
this.power = power;
this.speed = speed;
}
// データを適切な形式でもらえればDictionaryにキャストして渡すだけでモデルを初期化できる
public static void Init (List<object> list) {
fighters = new List<FighterModel> ();
foreach (Dictionary<string, object> dict in list) {
FighterModel figher = new FighterModel (dict);
fighters.Add (figher);
}
}
}