首页 > 解决方案 > Expression.Lambda 无法自动转换类型

问题描述

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

(Profiled,并且使用 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);

它会抛出一个异常:

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

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

如果我Expression.Convert用来转换类型,它会抛出 ArgumentException,告诉我该方法不能在System.Object.

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

标签: c#.netreflection

解决方案


有更好的表达式 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 委托,您必须缓存它以获得您正在寻找的性能提升。


推荐阅读