web-dev-qa-db-ja.com

C#で匿名オブジェクトのプロパティを反復処理するにはどうすればよいですか?

メソッドの引数として匿名オブジェクトを取得し、そのプロパティを反復処理して、各プロパティ/値を動的なExpandoObjectに追加します。

だから私が必要なのは

new { Prop1 = "first value", Prop2 = SomeObjectInstance, Prop3 = 1234 }

各プロパティの名前と値を知り、それらをExpandoObjectに追加できるようにします。

どうすればこれを達成できますか?

サイドノート:これは私のユニットテストの多くで行われます(セットアップで多くのジャンクをリファクタリングするために使用しています)ので、パフォーマンスはある程度関連しています。リフレクションについては確かに十分な知識がありませんが、私が理解したことから、それはかなりパフォーマンスが重いので、可能であればそれを避けたいです...

追加の質問:私が言ったように、この匿名オブジェクトをメソッドの引数として使用しています。メソッドの署名で使用するデータ型は何ですか? objectを使用すると、すべてのプロパティが利用可能になりますか?

53
Tomas Aschan
foreach(var prop in myVar.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
   Console.WriteLine("Name: {0}, Value: {1}",prop.Name, prop.GetValue(myVar,null));
}
68
BFree

匿名オブジェクトを反映してそのプロパティ名と値を取得し、実際に展開する辞書であるExpandoObjectを利用します。単体テストとして表現された例を次に示します。

    [TestMethod]
    public void ShouldBeAbleToConvertAnAnonymousObjectToAnExpandoObject()
    {
        var additionalViewData = new {id = "myControlId", css = "hide well"};
        dynamic result = new ExpandoObject();
        var dict = (IDictionary<string, object>)result;
        foreach (PropertyInfo propertyInfo in additionalViewData.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
        {
            dict[propertyInfo.Name] = propertyInfo.GetValue(additionalViewData, null);
        }
        Assert.AreEqual(result.id, "myControlId");
        Assert.AreEqual(result.css, "hide well");
    }
6

別のアプローチは、DynamicObjectの代わりにExpandoObjectを使用することです。この方法では、実際に他のオブジェクトからプロパティにアクセスしようとすると、リフレクションを行うオーバーヘッドしかありません。

public class DynamicForwarder : DynamicObject 
{
    private object _target;

    public DynamicForwarder(object target)
    {
        _target = target;
    }

    public override bool TryGetMember(
        GetMemberBinder binder, out object result)
    {
        var prop = _target.GetType().GetProperty(binder.Name);
        if (prop == null)
        {
            result = null;
            return false;
        }

        result = prop.GetValue(_target, null);
        return true;
    }
}

現在、動的なgetを介してプロパティに実際にアクセスしようとしたときにのみ反映されます。マイナス面として、同じプロパティに繰り返しアクセスする場合は、毎回反映する必要があります。したがって、結果をキャッシュできます。

public class DynamicForwarder : DynamicObject 
{
    private object _target;
    private Dictionary<string, object> _cache = new Dictionary<string, object>();

    public DynamicForwarder(object target)
    {
        _target = target;
    }

    public override bool TryGetMember(
        GetMemberBinder binder, out object result)
    {
        // check the cache first
        if (_cache.TryGetValue(binder.Name, out result))
            return true;

        var prop = _target.GetType().GetProperty(binder.Name);
        if (prop == null)
        {
            result = null;
            return false;
        }

        result = prop.GetValue(_target, null);
        _cache.Add(binder.Name, result); // <-------- insert into cache
        return true;
    }
}

ターゲットオブジェクトのリストの保存をサポートしてプロパティを結合し、プロパティの設定( TrySetMember と呼ばれる同様のオーバーライドを使用)をサポートして、キャッシュディクショナリに値を動的に設定できます。

もちろん、リフレクションのオーバーヘッドはおそらく心配する価値はありませんが、大きなオブジェクトの場合、その影響を制限できます。多分もっと面白いのは、それがあなたに与える特別な柔軟性です。

3

これは古い質問ですが、今では次のコードでこれを行うことができるはずです。

dynamic expObj = new ExpandoObject();
    expObj.Name = "James Kirk";
    expObj.Number = 34;

// print the dynamically added properties
// enumerating over it exposes the Properties and Values as a KeyValuePair
foreach (KeyValuePair<string, object> kvp in expObj){ 
    Console.WriteLine("{0} = {1} : Type: {2}", kvp.Key, kvp.Value, kvp.Value.GetType());
}

出力は次のようになります。

名前= James Kirk:タイプ:System.String

番号= 34:タイプ:System.Int32

2
raddevus

リフレクションを使用する必要があります。..( このURLからコード「借用」

using System.Reflection;  // reflection namespace

// get all public static properties of MyClass type
PropertyInfo[] propertyInfos;
propertyInfos = typeof(MyClass).GetProperties(BindingFlags.Public |
                                              BindingFlags.Static);
// sort properties by name
Array.Sort(propertyInfos,
        delegate(PropertyInfo propertyInfo1, PropertyInfo propertyInfo2)
        { return propertyInfo1.Name.CompareTo(propertyInfo2.Name); });

// write property names
foreach (PropertyInfo propertyInfo in propertyInfos)
{
  Console.WriteLine(propertyInfo.Name);
}
0
Muad'Dib

Reflection.Emitを使用して、ExpandoObjectを埋めるための汎用メソッドを作成します。

または、おそらく式を使用します(ただし、これは.NET 4でのみ可能だと思います)。

これらのアプローチはどちらも、呼び出し時にリフレクションを使用せず、デリゲートのセットアップ時のみ(明らかにキャッシュする必要がある)。

ディクショナリを埋めるためのReflection.Emitコードを次に示します(ExpandoObjectはそれほど遠くないはずです)。

static T CreateDelegate<T>(this DynamicMethod dm) where T : class
{
  return dm.CreateDelegate(typeof(T)) as T;
}

static Dictionary<Type, Func<object, Dictionary<string, object>>> cache = 
   new Dictionary<Type, Func<object, Dictionary<string, object>>>();

static Dictionary<string, object> GetProperties(object o)
{
  var t = o.GetType();

  Func<object, Dictionary<string, object>> getter;

  if (!cache.TryGetValue(t, out getter))
  {
    var rettype = typeof(Dictionary<string, object>);

    var dm = new DynamicMethod(t.Name + ":GetProperties", rettype, 
       new Type[] { typeof(object) }, t);

    var ilgen = dm.GetILGenerator();

    var instance = ilgen.DeclareLocal(t);
    var dict = ilgen.DeclareLocal(rettype);

    ilgen.Emit(OpCodes.Ldarg_0);
    ilgen.Emit(OpCodes.Castclass, t);
    ilgen.Emit(OpCodes.Stloc, instance);

    ilgen.Emit(OpCodes.Newobj, rettype.GetConstructor(Type.EmptyTypes));
    ilgen.Emit(OpCodes.Stloc, dict);

    var add = rettype.GetMethod("Add");

    foreach (var prop in t.GetProperties(
      BindingFlags.Instance |
      BindingFlags.Public))
    {
      ilgen.Emit(OpCodes.Ldloc, dict);

      ilgen.Emit(OpCodes.Ldstr, prop.Name);

      ilgen.Emit(OpCodes.Ldloc, instance);
      ilgen.Emit(OpCodes.Ldfld, prop);
      ilgen.Emit(OpCodes.Castclass, typeof(object));

      ilgen.Emit(OpCodes.Callvirt, add);
    }

    ilgen.Emit(OpCodes.Ldloc, dict);
    ilgen.Emit(OpCodes.Ret);

    cache[t] = getter = 
      dm.CreateDelegate<Func<object, Dictionary<string, object>>>();
  }

  return getter(o);
}
0
leppie