4

为了扩大我的技能,我正在尝试学习如何重写表达式。

目标:给定一个表达式,我想List.Contains()用调用我自己的静态方法替换实例InList。例如,以下两个表达式应该是等价的:

Expression<Func<Foo,bool>> expr1 = myRewriter.Rewrite(foo => fooList.Contains(foo));
Expression<Func<Foo,bool>> expr2 = foo => InList(foo, fooList);

我的尝试:我了解到使用自定义 ExpressionVisitor是基于现有表达式创建新表达式的最佳方式。但是,我一直无法构建一个MethodCallExpression实际调用我的方法的新方法。这是我尝试过的:

public class InListRewriter<T> : ExpressionVisitor
{
    public static bool InList(T target, List<T> source)
    {
        // this is my target method
        return true;
    }

    public Expression<Func<T, bool>> Rewrite(Expression<Func<T, bool>> expression)
    {
        return Visit(expression) as Expression<Func<T,bool>>;
    }

    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        // Only rewrite List.Contains()
        if (!node.Method.Name.Equals("Contains", StringComparison.InvariantCultureIgnoreCase))
            return base.VisitMethodCall(node);

        // Extract parameters from original expression
        var sourceList = node.Object;                   // The list being searched
        var target = node.Method.GetParameters()[0];    // The thing being searched for

        // Create new expression
        var type = typeof (InListRewriter<T>);
        var methodName = "InList";
        var typeArguments = new Type[] { };
        var arguments = new[] { Expression.Parameter(target.ParameterType, target.Name), sourceList };
        var newExpression = Expression.Call(type, methodName, typeArguments, arguments);

        return newExpression;
    }
}

但是,当我通过调用它时new InListRewriter<Foo>().Rewrite(foo => fooList.Contains(foo)),我得到一个InvalidOperationExceptionduring Expression.Call

类型 'MyNamespace.InListRewriter`1[MyNamespace.Foo]' 上没有方法 'InList' 与提供的参数兼容。

我什至尝试使用非常通用的签名制作一个新的 InList:

public static bool InList(params object[] things) {...}

但仍然收到同样的错误。我究竟做错了什么?我想做的甚至可能吗?

4

1 回答 1

5

您的代码有一个大问题:它传递的参数不正确,特别是第一个。

而不是Expression.Parameter(target.ParameterType, target.Name)您需要使用实际参数:node.Arguments[0].

此外,我建议您使用不同Expression.Call的重载:采用MethodInfo.

最终代码如下所示:

protected override Expression VisitMethodCall(MethodCallExpression node)
{
    // Only rewrite List.Contains()
    if (!node.Method.Name.Equals("Contains",
                                 StringComparison.InvariantCultureIgnoreCase))
        return base.VisitMethodCall(node);

    // Extract parameters from original expression
    var sourceList = node.Object;      // The list being searched
    var target = node.Arguments[0];    // The thing being searched for
    var newMethod = GetType().GetMethod("InList",
                                        BindingFlags.Static | BindingFlags.Public);

    // Create new expression
    var newExpression = Expression.Call(newMethod, target, sourceList);

    return newExpression;
}
于 2013-07-18T15:59:56.517 回答