Expression.Lambda无法自动转换类型

时间:2018-07-06 05:18:48

标签: c# .net reflection

我在许多类中都有一些属性,希望将它们的getter / setter编译为lambda表达式,以便可以使用性能更好的反射。

(分析后,使用Reflection的getValue / setValue大约占总运行时间的78%...)

但是,似乎Expression.Lambda()仅支持一个参数集合,并且不会自动转换参数类型。

using Exp = System.Linq.Expressions.Expression;
...
public class A { public int propA { get; set; } }
public class B { public int propB { get; set; } }
static Func<object, int> BuildFunc(Type type, string propName)
{
    var param = Exp.Parameter(prop.DeclaringType, "x");
    var exBody = Exp.Call(param, prop.GetGetMethod());
    return Exp.Lambda<Func<object, int>>(exBody, param).Compile();
}
...
var a = new A();
var b = new B();
var fA = BuildFunc(typeof(A).GetProperty("propA"));
var fB = BuildFunc(typeof(B).GetProperty("propB"));
fA(a);
fB(b);

它将阻止异常:

  

类型__Main__+A的ParametersExpression不能用于类型System.Object的委托参数

如果我将表达式更改为Exp.Lambda<Func<A, int>>(...),它将适用于A类,但不适用于B类。

如果我使用Expression.Convert来转换类型,它将抛出ArgumentException,告诉我该方法无法在System.Object的实例上调用。

那么我该怎么做才能像下面这样编译该表达式,它支持任何类型的对象和相应的方法? lambda = (object obj, MethodInfo method, ...) => { method.Invoke(obj, ...) }

1 个答案:

答案 0 :(得分:0)

有更好的表达式API可用于调用属性getter / setter方法。您绝对不必诉诸MethodInfo.Invoke

objectExpression.Convert的转换。您只需要将其插入正确的位置即可。

这是一个getter / setter委托编译示例,其中T是容器的类型(在示例中为AB)。

static Func<T, object> CompileGetter<T>(PropertyInfo property)
{
    // Target expression: (T obj) => (object)obj.Property;
    ParameterExpression objParam = Expression.Parameter(typeof(T), "obj");
    Expression body = Expression.Convert(Expression.MakeMemberAccess(objParam, property), typeof(object));

    return Expression
        .Lambda<Func<T, object>>(body, objParam)
        .Compile();
}

static Action<T, object> CompileSetter<T>(PropertyInfo property)
{
    // Target expression: (T obj, object value) => obj.Property = (TProperty)value;
    ParameterExpression objParam = Expression.Parameter(typeof(T), "obj");
    ParameterExpression valueParam = Expression.Parameter(typeof(object), "value");

    Expression body = Expression.Assign(
        Expression.MakeMemberAccess(objParam, property),
        Expression.Convert(valueParam, property.PropertyType)
    );

    return Expression
        .Lambda<Action<T, object>>(body, objParam, valueParam)
        .Compile();
}

证明:

class Dummy
{
    public int Value { get; set; }
}

...

PropertyInfo prop = typeof(Dummy).GetProperty("Value");
Func<Dummy, object> getter = CompileGetter<Dummy>(prop);
Action<Dummy, object> setter = CompileSetter<Dummy>(prop);

Dummy d = new Dummy { Value = 123 };

Assert.AreEqual(123, getter(d));

setter(d, 321);

Assert.AreEqual(321, d.Value);

请注意:LambdaExpression.Compile是一项占用大量CPU的操作,因此,一旦创建了getter / setter委托,就必须对其进行缓存,以提高性能。