在不知道编译时目标类型的情况下,使用表达式获取属性值

3
我正在尝试创建一个表达式lambda,以传递一个对象,然后返回命名属性的值。但是该类型只在运行时才知道。
我从以下方法开始处理在编译时已知类型:
private static Func<T, object> CreateExpression(string propertyName)
{
    var arg = Expression.Parameter(typeof(T));
    var expr = Expression.Property(arg, propertyName);
    return Expression.Lambda<Func<T, object>>(expr, arg).Compile();
}

这个代码很完美。但是,我需要将其更改为处理仅在运行时已知的类型。

我应该可以像这样调用委托:

public object GetPropertyValue(object obj)
{
    var propertyDelegate = GetDelegate(typeof(obj));        
    var propertyValue = propertyDelegate (obj);
    return propertyValue;
}

private Func<object, object> GetDelegate(Type type)
{
    // Lookup delegate in dictionary, or create if not existing
    return CreateDelegate("MyProperty", type);
}

我尝试更改之前的CreateDelegate,但它无法与Func<object, object>一起使用:

Func<object,object> CreateDelegate(string propertyName, Type targetType)
{
    var arg = Expression.Parameter(type);
    var body = Expression.Property(arg, name);

    var lambda = Expression.Lambda<Func<object,object>>(body, arg); //ArgumentException
    return lambda.Compile();
}

它不会接受Expression.Parameter,因为它的类型是'targetType'而不是'object'。

我需要使用Expression.Convert或其他什么东西吗?

注意:委托将被多次调用(过滤方法),因此需要编译以确保性能。

编辑:解决方案(由Marc Gravell提供)

变量'body'应更改为以下内容:

var body = Expression.Convert(
             Expression.Property(
               Expression.Convert(arg, type), 
               name), 
             typeof(object));

内部的 Convert 将输入参数转换为对象,而外部的 Convert 则将返回值进行转换。


有一个非泛型版本的 Expression.Lambda,你可能需要查看一下。 - Rhumborl
1
@Rhumborl,这样做几乎没有什么好处;你需要一个Func<object,object>来提高性能;使用DynamicInvokeDelegate非常慢。 - Marc Gravell
我考虑过这个问题,但是我只能使用DynamicInvoke来调用委托,这会导致速度非常慢。 :-( - Anders
1个回答

3

是的:

var arg = Expression.Parameter(typeof(object));
var expr = Expression.Property(Expression.Convert(arg, type), propertyName);

注意:返回类型 (object) 意味着许多类型需要被装箱。由于您提到您正在进行过滤:如果可能的话,请尝试避免通过创建一个内部执行任何比较等操作而不需要装箱的 Func<object,bool> 来进行装箱。

这个完美运作! :) 你会如何添加表达式Equal和另一个参数进行比较? 我猜是一个Func<object,object,bool> (obj, value, result)? - Anders
@Nautious 嗯,那么你每次都会将输入装箱,所以可能没有什么收益;我想更多地考虑 Expression.Equal(..., Expression.Constant(expectedValue)) - Marc Gravell
在尝试了一下之后,我仍然遇到了返回类型的异常,就像你在注释中提到的那样。 显然,它不介意返回字符串和URI,但是TimeSpan、Int32和Double会抛出ArgumentException,因此需要对body进行装箱处理。 :/ Body部分将是以下内容:var body = Expression.Convert(Expression.Property(Expression.Convert(arg, type), name), typeof(object); - Anders
@Nautious 我以为它会自动装箱,但似乎不是这样 :( - Marc Gravell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接