Как мне перебрать свойства анонимного объекта в C #? - PullRequest
53 голосов
/ 07 апреля 2010

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

Так что мне нужно, чтобы перейти от

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

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

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

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

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

Ответы [ 6 ]

68 голосов
/ 07 апреля 2010
foreach(var prop in myVar.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
   Console.WriteLine("Name: {0}, Value: {1}",prop.Name, prop.GetValue(myVar,null));
}
6 голосов
/ 24 июля 2013

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

Альтернативный подход заключается в использовании 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;
    }
}

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

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

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

2 голосов
/ 23 ноября 2016

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

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

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

Имя = Джеймс Кирк: Тип: System.String

Номер = 34: Тип: System.Int32

0 голосов
/ 07 апреля 2010

Используйте 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 голосов
/ 07 апреля 2010

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