Reading Properties of an Object with Expression Tr

2019-02-02 16:55发布

问题:

I want to create a Lambda Expression for every Property of an Object that reads the value dynamically.

What I have so far:

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

The code Works well when i call functionThatGetsValue as long as "TypeOfProperty" is hardcoded. I know that I can't pass the "TypeOfPoperty" dynamically. What can I do to achive my goal?

回答1:

Assuming that you're happy with a Func<TType, object> delegate (as per the comments above), you can use Expression.Convert to achieve that:

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


回答2:

After hours of googling found the answer here. I've added the snippets from the blog post as it might help others having the same troubles:

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


回答3:

I've modified gsharp's post above to actually set the value directly and make it a bit easier to use. It's not ideal as there is the introduction of the DynamicCast function which requires you to know your type up front. My goal was to try to keep us strongly typed and not return object and avoid dynamic keyword. Also, keep "magic" to a minimum.

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

Examples:

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

You could write a helper method which uses MakeGenericMethod or an expression tree to make a lambda do the typed call to call DynamicCast based on the PropertyInfo object and avoid having to know it up front. But that is less elegant.