2010-04-07 4 views
50

메서드에 대한 인수로 익명 개체를 가져온 다음 해당 속성을 반복하여 각 속성/값을 동적 인 ExpandoObject에 추가하려고합니다.C#에서 익명 개체의 속성을 반복하는 방법은 무엇입니까?

그래서 내가 필요한 것은 각 속성의 이름과 값을 알고, 그리고 ExpandoObject에 추가 할 수있는에

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

에서 이동하는 것입니다.

어떻게해야합니까?

사이드 노트 : 이것은 많은 단위 테스트에서 수행됩니다. (설정에서 많은 정크를 리팩토링하는 데 사용하고 있습니다.) 성능이 어느 정도 관련되어 있습니다. 나는 확실히 말할 반사에 대해 충분히 잘 모르겠지만, 내가 이해 한 것과 ... 그것은 꽤 성능 무거운, 그래서 가능하다면 차라리 그것을 피할 것

후속 질문 : 내가 말했듯이, 나는이 익명 객체를 메소드의 인수로 사용합니다. 메소드의 서명에 어떤 데이터 유형을 사용해야합니까? object을 사용하면 모든 속성을 사용할 수 있습니까? 당신이 반사를 사용할 필요가

+1

반사 성능은 실제로 너무 끔찍하지 않습니다. 이 작업을 수행해야하는 인스턴스가 매우 많은 경우 지정된 익명 형식의 PropertyInfo 항목을 캐시 한 다음 해당 PropertyInfo 항목을 반복하여 각 인스턴스의 속성을 확인할 수 있습니다. 각 속성에 대해 GetMethod에 대한 대리자를 만들고 캐시 할 수도 있습니다. –

답변

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

좋아요, 리플렉션을 사용해야합니다. 이것이 성능 문제일까요? 또한 ExpandoObject에 추가 할 수있는 양식의 속성 이름을 얻는 방법은 무엇입니까? –

+1

나는 다음과 같은 질문에 대한 문제를 해결했다 :'(MyExpandoObject as ICollection >). 감사! –

+0

원칙적으로 이것을 수행하기 위해 반사가 필요하지 않습니다. 컴파일 타임 루핑 기능이 제공되면 동일한 결과가 더 효율적으로 달성 될 수 있으며 인트로 스펙 션은 컴파일 타임에만 국한 될 수 있습니다. – pyon

0

.... (code "borrowed" from this 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

OK - 리플렉션을 사용해야합니다. 대부분의 단위 테스트에서 테스트마다 한 번 수행하면 성능 문제가 발생합니까? –

+1

@ 토마스 : 그런 양의 정보로는 그런 종류의 질문에 대답 할 방법이 없습니다. 그냥 시도해보십시오. –

1

사용 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); 
} 
3

다른 방법 대신 ExpandoObjectDynamicObject을 사용하는 것입니다, 당신은 실제로 다른 객체에서 속성에 액세스하려고하면 그 방법 만 반사를 수행의 오버 헤드를 가지고있다.

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라는 유사한 오버라이드) 속성을 속성을 병합 할 대상 개체 목록을 저장 및 지원 설정을 지원할 수 : 그래서 당신은 결과를 캐시 할 수 사전.

물론 반사의 오버 헤드는 걱정할 가치가 없을 것입니다. 그러나 큰 물체의 경우에는 이로 인해 영향이 제한 될 수 있습니다. 어쩌면 더 흥미로운 것은 그것이 당신에게주는 유연성입니다.

6

속성 이름과 값을 얻기 위해 익명 객체를 반영한 ​​다음 ExpandoObject를 실제로 채울 수있는 사전 인 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"); 
    } 
2

이 오래된 질문이지만, 지금은 다음 코드를 사용하여이 작업을 수행 할 수 있어야한다 :

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 : 유형 : 시스템 .Int32