【问题标题】:Rewrite expression to replace List.Contains with custom method重写表达式以用自定义方法替换 List.Contains
【发布时间】:2013-07-19 00:05:53
【问题描述】:

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

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

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

我的尝试:我了解到使用custom 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&lt;Foo&gt;().Rewrite(foo =&gt; fooList.Contains(foo)) 调用它时,我在Expression.Call 期间得到一个InvalidOperationException

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

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

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

但仍然收到相同的错误。我究竟做错了什么?我想做的事有可能吗?

【问题讨论】:

  • 代码看起来没问题。查看您作为参数传入的表达式的类型。它们匹配 (Foo, List) 吗?
  • @usr 在执行丹尼尔的回答并发现我有第二个问题之后,原来我的第二个问题是我不小心引用了foo.SomeParam 而不是foo,因此类型不匹配和我得到的错误。所以你也是对的。

标签: c# expression


【解决方案1】:

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

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

此外,我建议您使用 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;
}

【讨论】:

  • 是的,但Expression.Call 调用仍应通过,因为类型匹配。
  • @usr:我不认为 OP 得到的异常来自他展示的代码。当我执行他的代码时,我得到了一个完全不同的代码,它更符合他的代码的实际问题:InvalidOperationException: variable 'item' of type 'UserQuery+Foo' referenced from scope '', but it is not defined 。解决方法调用表达式的第一个参数的问题消除了该错误并且代码有效。
  • 对所有帐户都正确。我更改了访问者以匹配您的访问者并开始收到不同的错误。但是,将我的代码拉出到一个更简单的文件中可以使其正确运行,因此我的代码在我的重写器之外还有其他问题。我最终会弄清楚的。无论如何,你肯定回答了我的问题,谢谢。
猜你喜欢
  • 2017-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多