2013-05-08 12 views
8

Desidero creare un'espressione lambda per ogni proprietà di un oggetto che legge il valore in modo dinamico.Lettura delle proprietà di un oggetto con alberi di espressione

Quello che ho finora:

var properties = typeof (TType).GetProperties().Where(p => p.CanRead); 

foreach (var propertyInfo in properties) 
{ 
    var getterMethodInfo = propertyInfo.GetGetMethod(); 

    var entity = Expression.Parameter(typeof (TType)); 

    var getterCall = Expression.Call(entity, getterMethodInfo); 

    var lambda = Expression.Lambda(getterCall, entity); 
    var expression = (Expression<Func<TType, "TypeOfProperty">>) lambda; 
    var functionThatGetsValue = expression.Compile(); 
} 

il codice funziona bene quando chiamo functionThatGetsValue finché "TypeOfProperty" è hardcoded. So che non posso passare dinamicamente "TypeOfPoperty". Cosa posso fare per raggiungere il mio obiettivo?

+0

Qual è il tuo obiettivo? Dici che vuoi creare un'espressione lambda; hai solo bisogno del delegato compilato ('functionThatGetsValue'), o ti serve anche l'albero delle espressioni intermedie (' expression')? – LukeH

+0

@LukeH, solo il delegato compilato. Grazie. (Il mio obiettivo è quello di scorrere un elenco di oggetti e leggere tutti i valori delle proprietà. Per ottenere un po 'di prestazioni, voglio farlo in questo modo invece di usare il riflesso) – gsharp

+1

Quando ho cercato di ottenere risultati simili, ho finito con restituendo Func e inoltrando il valore restituito a un tipo di proprietà specifico sul lato del chiamante. –

risposta

7

Supponendo che sei felice con un Func<TType, object> delegato (secondo le osservazioni di cui sopra), è possibile utilizzare Expression.Convert per raggiungere questo:

var properties = typeof(TType).GetProperties().Where(p => p.CanRead); 

foreach (var propertyInfo in properties) 
{ 
    MethodInfo getterMethodInfo = propertyInfo.GetGetMethod(); 
    ParameterExpression entity = Expression.Parameter(typeof(TType)); 
    MethodCallExpression getterCall = Expression.Call(entity, getterMethodInfo); 

    UnaryExpression castToObject = Expression.Convert(getterCall, typeof(object)); 
    LambdaExpression lambda = Expression.Lambda(castToObject, entity); 

    var functionThatGetsValue = (Func<TType, object>)lambda.Compile(); 
} 
5

Dopo ore di googling trovato la risposta here. Ho aggiunto i frammenti di testo del post sul blog come potrebbe aiutare gli altri con le stesse difficoltà:

public static class PropertyInfoExtensions 
{ 
    public static Func<T, object> GetValueGetter<T>(this PropertyInfo propertyInfo) 
    { 
     if (typeof(T) != propertyInfo.DeclaringType) 
     { 
      throw new ArgumentException(); 
     } 

     var instance = Expression.Parameter(propertyInfo.DeclaringType, "i"); 
     var property = Expression.Property(instance, propertyInfo); 
     var convert = Expression.TypeAs(property, typeof(object)); 
     return (Func<T, object>)Expression.Lambda(convert, instance).Compile(); 
    } 

    public static Action<T, object> GetValueSetter<T>(this PropertyInfo propertyInfo) 
    { 
     if (typeof(T) != propertyInfo.DeclaringType) 
     { 
      throw new ArgumentException(); 
     } 

     var instance = Expression.Parameter(propertyInfo.DeclaringType, "i"); 
     var argument = Expression.Parameter(typeof(object), "a"); 
     var setterCall = Expression.Call(
      instance, 
      propertyInfo.GetSetMethod(), 
      Expression.Convert(argument, propertyInfo.PropertyType)); 
     return (Action<T, object>)Expression.Lambda(setterCall, instance, argument).Compile(); 
    } 
} 
0

Ho modificato il post di cui sopra per gsharp realtà impostare direttamente il valore e renderlo un po 'più facile da usare. Non è l'ideale in quanto vi è l'introduzione della funzione DynamicCast che richiede di conoscere il tuo tipo in anticipo. Il mio obiettivo era quello di cercare di mantenerli fortemente digitati e non restituire oggetti ed evitare parole chiave dinamiche. Inoltre, mantieni la "magia" al minimo.

public static T DynamicCast<T>(this object value) 
    { 
     return (T) value; 
    } 
    public static object GetPropertyValue<T>(this PropertyInfo propertyInfo, T objectInstance) 
    { 
     if (typeof(T) != propertyInfo.DeclaringType) 
     { 
      throw new ArgumentException(); 
     } 

     var instance = Expression.Parameter(propertyInfo.DeclaringType, "i"); 
     var property = Expression.Property(instance, propertyInfo); 
     var convert = Expression.TypeAs(property, propertyInfo.PropertyType); 
     var lambda = Expression.Lambda(convert, instance).Compile(); 
     var result = lambda.DynamicInvoke(objectInstance); 


     return result; 
    } 

    public static void SetPropertyValue<T, TP>(this PropertyInfo propertyInfo, T objectInstance, TP value) 
     where T : class 
     where TP : class 
    { 
     if (typeof(T) != propertyInfo.DeclaringType) 
     { 
      throw new ArgumentException(); 
     } 

     var instance = Expression.Parameter(propertyInfo.DeclaringType, "i"); 
     var argument = Expression.Parameter(propertyInfo.PropertyType, "a"); 
     var setterCall = Expression.Call(
      instance, 
      propertyInfo.GetSetMethod(), 
      Expression.Convert(argument, propertyInfo.PropertyType)); 

     var lambda = Expression.Lambda(setterCall, instance, argument).Compile(); 
     lambda.DynamicInvoke(objectInstance, value); 
    } 

Esempi:

 public void Get_Value_Of_Property() 
    { 
     var testObject = new ReflectedType 
     { 
      AReferenceType_No_Attributes = new object(), 
      Int32WithRange1_10 = 5, 
      String_Requires = "Test String" 
     }; 

     var result = testObject.GetType().GetProperty("String_Requires").GetPropertyValue(testObject).DynamicCast<string>(); 

     result.Should().Be(testObject.String_Requires); 
    } 

    public void Set_Value_Of_Property() 
     { 
      var testObject = new ReflectedType 
      { 
       AReferenceType_No_Attributes = new object(), 
       Int32WithRange1_10 = 5, 
       String_Requires = "Test String" 
      }; 

      testObject.GetType().GetProperty("String_Requires").SetPropertyValue(testObject, "MAGIC"); 

      testObject.String_Requires.Should().Be("MAGIC"); 
     } 

È potrebbe scrivere un metodo di supporto che utilizza MakeGenericMethod o un albero di espressione per fare un lambda fare la chiamata digitato per chiamare dynamic_cast in base all'oggetto PropertyInfo ed evitare di dover per conoscerlo in anticipo. Ma questo è meno elegante.

Problemi correlati