2010-04-07 14 views
50

Voglio prendere un oggetto anonimo come argomento di un metodo, quindi scorrere le sue proprietà per aggiungere ogni proprietà/valore ad un dinamico ExpandoObject.Come faccio a scorrere le proprietà di un oggetto anonimo in C#?

Così che cosa ho bisogno è di andare da

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

a conoscere i nomi ei valori di ogni proprietà, e di essere in grado di aggiungerli alla ExpandoObject.

Come posso realizzare questo?

Nota a margine: Questo sarà fatto in molti dei miei test di unità (lo sto usando per rifattorare molta roba nel setup), quindi le prestazioni sono in qualche misura rilevanti. Non so abbastanza di riflessione per dirlo con certezza, ma da quello che ho capito è abbastanza pesante prestazioni, quindi se è possibile, preferirei evitarlo ...

Follow-up domanda: Come ho detto, sto prendendo questo oggetto anonimo come argomento per un metodo. Quale tipo di dati dovrei usare nella firma del metodo? Tutte le proprietà saranno disponibili se utilizzo object?

+1

Le prestazioni di riflessione non sono poi così terribili. Se si dispone di un numero molto elevato di istanze a cui è necessario eseguire questa operazione, è possibile memorizzare nella cache le voci PropertyInfo per un determinato tipo anonimo e quindi reiterare su tali voci PropertyInfo per risolvere le proprietà per ciascuna istanza. È anche possibile creare delegati per GetMethod per ciascuna proprietà e memorizzarli nella cache. –

risposta

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

OK, devo usare la riflessione. Sarà un problema di prestazioni? Inoltre, come ottenere il nome della proprietà in un modulo che è possibile utilizzare per aggiungere a ExpandoObject? –

+1

Ho risolto il problema della domanda successiva: '(MyExpandoObject as ICollection >). Add (...)' ha fatto il trucco. Grazie! –

+0

In linea di principio, la riflessione non dovrebbe essere necessaria per fare ciò. Se è stata fornita una funzionalità di loop in fase di compilazione, lo stesso risultato potrebbe essere ottenuto in modo più efficiente e l'introspezione potrebbe essere limitata al tempo di compilazione. – pyon

0

si deve usare riflessione .... (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 - Devo usare la riflessione. Questo sarà un problema di prestazioni se lo faccio nella maggior parte dei miei test unitari, una volta per test? –

+1

@Tomas: Non c'è modo di rispondere a quel tipo di domande con quella quantità di informazioni. Provalo e guarda. –

1

Usa Reflection.Emit per creare un metodo generico per riempire un ExpandoObject.

OPPURE utilizzare le espressioni (penso che ciò sarebbe possibile solo in .NET 4).

Nessuno di questi approcci utilizza la riflessione durante il richiamo, solo durante l'installazione di un delegato (che ovviamente deve essere memorizzato nella cache).

Ecco qualche codice Reflection.Emit per riempire un dizionario (suppongo che ExpandoObject non sia lontano);

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

Un approccio alternativo è quello di utilizzare al posto di DynamicObjectExpandoObject, e in questo modo si ha solo la testa di fare la riflessione se effettivamente tenta di accedere a una proprietà da l'altro oggetto.

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

Ora fa solo il riflesso quando si tenta effettivamente di accedere alla proprietà tramite un get dinamico. Sul lato negativo, se si accede ripetutamente alla stessa proprietà, deve fare il riflesso ogni volta. Così si potrebbe memorizzare nella cache il risultato:

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

Si potrebbe sostenere la memorizzazione di un elenco di oggetti di destinazione a fondersi le loro proprietà, e l'impostazione di supporto proprietà (con un override di simile chiamato TrySetMember) per consentire di valori di impostare dinamicamente nella cache dizionario.

Naturalmente, il sovraccarico di riflessione probabilmente non vale la pena di preoccuparsi, ma per gli oggetti di grandi dimensioni questo potrebbe limitare l'impatto di esso. Ciò che è forse più interessante è la flessibilità extra che ti dà.

6

Rifletti sull'oggetto anonimo per ottenere i suoi nomi e valori di proprietà, quindi sfrutta un ExpandoObject che in realtà è un dizionario per popolarlo.Ecco un esempio, espresso in una prova di unità:

[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

Questa è una vecchia questione, ma ora si dovrebbe essere in grado di fare questo con il seguente codice:

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

L'output sarà simile al seguente:

Name = James Kirk: Tipo: System.String

Numero = 34: Tipo: sistema .Int32

Problemi correlati