コガネブログ

平日更新を目標にUnityやC#、Visual Studioなどのゲーム開発アレコレを書いていきます

【C#】リフレクションを使用してToString関数を手軽に実装する

// キャラクター
public class Character
{
    public string Name;  // 名前
    public int    Level; // レベル
}

上記のようなキャラクターの情報を管理するクラスがあったとして
このクラスのインスタンスの情報をログに出力したい場合に下記のような構文を書くと

var character = new Character { Name = "ピカチュウ", Level = 5 };
Debug.Log(character);
// Character

ログにはクラス名である"Character"と表示されるだけで
インスタンスの情報(Characterクラスの場合は名前やレベル)は出力されません

これを防ぐためにはToString関数をオーバーライドして
必要な情報を文字列に整形する処理を書く必要があります

// キャラクター
public class Character
{
    public string Name;  // 名前
    public int    Level; // レベル

    public override string ToString()
    {
        var builder = new StringBuilder();
        builder.AppendFormat("Name:{0},", Name);
        builder.AppendFormat("Level:{0}", Level);
        return builder.ToString();
    }
}

var character = new Character { Name = "ピカチュウ", Level = 5 };
Debug.Log(character);
// Name:ピカチュウ,Level:5

しかし、新しくクラスを定義するたびにToString関数をオーバーライドするのはとても面倒です
変数やプロパティが追加されるたびにToString関数も編集する必要があります

そこで、C#のリフレクションという機能を使うと少ない労力で
すべてのクラスに変数やプロパティの情報を文字列に整形する機能を持たせることができます

/// <summary>
/// object型の拡張メソッドを管理するクラス
/// </summary>
public static class ObjectExtensions
{
    private const string SEPARATOR = ",";       // 区切り記号として使用する文字列
    private const string FORMAT = "{0}:{1}";    // 複合書式指定文字列
 
    /// <summary>
    /// すべての公開フィールドの情報を文字列にして返します
    /// </summary>
    public static string ToStringFields<T>(this T obj)
    {
        return string.Join(SEPARATOR, obj
            .GetType()
            .GetFields(BindingFlags.Instance | BindingFlags.Public)
            .Select(c => string.Format(FORMAT, c.Name, c.GetValue(obj))));
    }
    
    /// <summary>
    /// すべての公開プロパティの情報を文字列にして返します
    /// </summary>
    public static string ToStringProperties<T>(this T obj)
    {
        return string.Join(SEPARATOR, obj
            .GetType()
            .GetProperties(BindingFlags.Instance | BindingFlags.Public)
            .Where(c => c.CanRead)
            .Select(c => string.Format(FORMAT, c.Name, c.GetValue(obj, null))));
    }
    
    /// <summary>
    /// すべての公開フィールドと公開プロパティの情報を文字列にして返します
    /// </summary>
    public static string ToStringReflection<T>(this T obj)
    {
        return string.Join(SEPARATOR, 
            obj.ToStringFields(), 
            obj.ToStringProperties());
    }
}

上記のObjectExtensionsのようなクラスを作成しておくことで

// キャラクター
public class Character
{
    public string Name;  // 名前
    public int    Level; // レベル
}

var character = new Character { Name = "ピカチュウ", Level = 5 };
Debug.Log(character.ToStringReflection());
// Name:ピカチュウ,Level:5

独自に定義したクラスに毎回ToString関数をオーバーライドさせることなく
インスタンスの情報を文字列に整形する機能を持たせることができます