用表达式树读取对象的属性

本文关键字:属性 取对象 读取 表达式 | 更新日期: 2023-09-27 18:12:30

我想为一个动态读取值的对象的每个属性创建一个Lambda表达式。

目前为止我有什么:

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

当我调用functionThatGetsValue时,只要"TypeOfProperty"是硬编码,代码就能很好地工作。我知道我不能动态地传递"typeofpproperty"。我能做些什么来实现我的目标?

用表达式树读取对象的属性

假设您对Func<TType, object>委托很满意(根据上面的评论),您可以使用Expression.Convert来实现:

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

经过几个小时的谷歌搜索,我在这里找到了答案。我从博客文章中添加了一些片段,因为它可能会帮助其他人遇到同样的麻烦:

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

我已经修改了gsharp上面的帖子,实际设置直接的值,使它更容易使用。这并不理想,因为它引入了DynamicCast函数,要求您事先知道自己的类型。我的目标是尽量保持强类型,不返回对象,避免动态关键字。同时,把"魔法"保持在最低限度。

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

例子:

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

可以编写一个助手方法,它使用MakeGenericMethod或表达式树使lambda基于PropertyInfo对象执行类型化调用以调用DynamicCast,而不必事先知道它。