【C#】リフレクションでオブジェクトの値を列挙する

C# のリフレクションという機能を使用して任意の型の内容、public なフィールドとプロパティをすべて列挙してみようと思います。

リフレクションを使用するので、特定の型のメンバーを認識したうえでフィールドやプロパティの名前を指定して列挙するのではなく、どのオブジェクトでも自由に列挙することが可能です。

確認環境

  • .NET core 3.1
  • VisualStudio2022
  • Windows11

コンソールアプリで確認しています。

実装コード

Sampleクラス

まず内容を表示するための以下クラスを作成します。

// Sample.cs

public class Sample
{
    public string Foo { get; set; }
    public string Bar { get; set; }
    public readonly string Mes = "asdf";
    public int A = 0;
} 

ObjectReaderクラス

次にオブジェクトの列挙する処理を実装するクラスです。

// ObjectReader.cs

using System;
using System.Collections.Generic;
using System.Reflection;

public class ObjectReader
{
    public IEnumerable<object[]> ReadObjects<T>(IEnumerable<T> items)
    {
        Type type = typeof(T);

        // ここで列挙する内容を指定する
        PropertyInfo[] pis = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
        FieldInfo[] fis = type.GetFields(BindingFlags.Instance | BindingFlags.Public);
        foreach (T item in items)
        {
            yield return ReadObject(item, type, pis, fis);
        }
    }

    public object[] ReadObject<T>(T obj)
    {
        Type type = typeof(T);

        // ここで列挙する内容を指定する
        PropertyInfo[] pis = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
        FieldInfo[] fis = type.GetFields(BindingFlags.Instance | BindingFlags.Public);
        return ReadObject(obj, type, pis, fis);
    }

    private object[] ReadObject(object obj, Type type, PropertyInfo[] pis, FieldInfo[] fis)
    {
        object[] retItems = new object[pis.Length + fis.Length];

        // プロパティの列挙
        for (int i = 0; i < pis.Length; i++)
        {
            PropertyInfo pi = pis[i];
            retItems[i] = pi.GetValue(obj);
        }

        // フィールドの列挙
        for (int i = 0; i < fis.Length; i++)
        {
            FieldInfo fi = fis[i];
            retItems[pis.Length + i] = fi.GetValue(obj);
        }

        return retItems;
    }
}

使用方法

使い方は以下の通りです。

private static void Test()
{
    ObjectReader r = new ObjectReader();

    // 単体のオブジェクトの内容の列挙
    var a = new Sample()
    {
        Bar = "bar",
        Foo = "foo",
    };
    object[] values = r.ReadObject(a);
    Console.WriteLine(string.Join(", ", values));

    // 複数オブジェクトの内容の列挙
    var items = new List<Sample>()
    {
        a,
        new Sample() { Bar = "bar2", Foo = "foo2" },
        new Sample() { Bar = "bar3", Foo = "foo3" },
    };
    var result = r.ReadObjects(items);
    foreach (var item in result) 
    {
        Console.WriteLine(string.Join(", ", item));
    }
}

privateメンバーも表示する

上記実装例は public のみ取得していましたが、private メンバーも取得するには BindingFlags の指定を変更します。

// publicメンバーだけ取得
PropertyInfo[] pis = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
FieldInfo[] fis = type.GetFields(BindingFlags.Instance | BindingFlags.Public);

// privateメンバーも取得する
PropertyInfo[] pis = 
    type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
FieldInfo[] fis = 
    type.GetFields(BindingFlags.Instance | BindingFlags.Public| BindingFlags.NonPublic);

特定のメンバーだけ取得しない

System.Runtime.Serialization.IgnoreDataMemberAttribute という属性がついているメンバーの値を取得すしないようにするためには以下のような実装にします。

ObjectReader の ReadObject を以下の通り特定の属性を持っているかどうかに変更します。

private object[] ReadObject(object obj, Type type, PropertyInfo[] pis, FieldInfo[] fis)
{
    var retList = new List<object>(); // 量が分からないのでListに変更

    // プロパティの列挙
    for (int i = 0; i < pis.Length; i++)
    {
        PropertyInfo pi = pis[i];
        if (pi.IsDefined(IgnoreType, false)) // 指定した属性がついてる?
        {
            continue;
        }
        retList.Add(pi.GetValue(obj));
    }

    // フィールドの列挙
    for (int i = 0; i < fis.Length; i++)
    {
        FieldInfo fi = fis[i];
        if (fi.IsDefined(IgnoreType, false)) // 指定した属性がついてる?
        {
            continue;
        }
        retList.Add(fi.GetValue(obj));
    }

    return retList.ToArray();
}