Как выполнить итерацию свойств анонимного объекта в C#?


Я хочу взять анонимный объект в качестве аргумента метода, а затем перебирать его свойства, чтобы добавить каждое свойство / значение в динамический ExpandoObject.

так что мне нужно ехать из

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

чтобы знать имена и значения каждого свойства, и возможность добавлять их в ExpandoObject.

как мне это сделать?

боковое Примечание: это будет сделано во многих моих модульных тестах (я использую его для рефакторинга большого количества мусора в настройка), поэтому производительность в некоторой степени актуальна. Я не знаю достаточно об отражении, чтобы сказать наверняка, но из того, что я понял, это довольно тяжелая работа, поэтому, если это возможно, я предпочел бы избежать этого...

следующий вопрос: Как я уже сказал, я беру этот анонимный объект в качестве аргумента метода. Какой тип данных следует использовать в сигнатуре метода? Будут ли доступны все свойства, если я использую object?

6 51

6 ответов:

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

поразмышляйте над анонимным объектом, чтобы получить его имена свойств и значения, а затем воспользуйтесь тем, что 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");
    }

альтернативный подход заключается в использовании 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), чтобы позволить вам динамически устанавливать значения в словаре кэша.

конечно, накладные расходы на отражение, вероятно, не стоит беспокоиться, но для больших объектов это может ограничить его влияние. Что, возможно, более интересно, так это дополнительная гибкость, которую она дает вам.

Это старый вопрос, но теперь вы должны быть в состоянии сделать это с помощью следующего кода:

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());
}

вывод будет выглядеть следующим образом:

Name = James Kirk: Type: System.Строка

Число = 34: Тип: System. Int32

Использовать Отражение.Эмиссия для создания универсального метода заполнения объекта ExpandoObject.

или использовать выражения (я думаю, что это будет возможно только в .4 чистая правда).

ни один из этих подходов использует отражение при вызове, только во время установки делегата (который, очевидно, должен быть кэширован).

вот некоторые размышления.Выделите код для заполнения словаря (я думаю, 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);
}

вы должны использовать отражение.... ( код "заимствован" из этого 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);
}