如何从 linq 表达式中获取字符串?

Rec*_*ğdu 5 .net c# linq linq-expressions

我有这个方法和参数。

void SomeMethod(Expression<Func<Products, bool>> where)
Run Code Online (Sandbox Code Playgroud)

我这样称呼这个方法;

int i = 9;
SomeMethod(x=>x.Id==i)
Run Code Online (Sandbox Code Playgroud)

我希望它产生这个字符串;

"x=>x.Id==9"
Run Code Online (Sandbox Code Playgroud)

如果我只是按原样打印出上面的表达式,它会给我这个字符串:

"x => (x.Id == value(isTakibi.WepApp.Controllers.HomeController+<>c__DisplayClass4_0).i)"
Run Code Online (Sandbox Code Playgroud)

但我需要“x.Id == 9”。我需要评估变量的值,i以便结果为“x.id==9”。

Ser*_*rvy 5

通常简化表达式的方法是编译它并执行编译后的委托。现在,您不能对仍然包含任何参数表达式的任何表达式执行此操作,因为您(尚)不知道参数的值是什么。这意味着我们有两个基本步骤,首先,确定我们树中的哪些子表达式实际上包含该子树中某处的参数,然后评估所有不包含的参数。

因此,第一步是确定哪些表达式中包含参数。为此,我们创建了一个表达式访问者,该访问者具有一个字段,该字段指示它当前是否在带有参数的子树内,然后递归检查其子树,然后检查自身,然后组合结果,将所有无参数表达式添加到集合中,同时一路上。

private class ParameterlessExpressionSearcher : ExpressionVisitor
{
    public HashSet<Expression> ParameterlessExpressions { get; } = new HashSet<Expression>();
    private bool containsParameter = false;

    public override Expression Visit(Expression node)
    {
        bool originalContainsParameter = containsParameter;
        containsParameter = false;
        base.Visit(node);
        if (!containsParameter)
        {
            if (node?.NodeType == ExpressionType.Parameter)
                containsParameter = true;
            else
                ParameterlessExpressions.Add(node);
        }
        containsParameter |= originalContainsParameter;

        return node;
    }
}
Run Code Online (Sandbox Code Playgroud)

接下来评估没有参数的子表达式,我们需要另一个访问者。这个只需要检查表达式是否在我们在前一个访问者处找到的表达式集中,如果是,则将该表达式编译为无参数委托并执行它,否则它将检查其子代中是否有任何一个可以更换。

private class ParameterlessExpressionEvaluator : ExpressionVisitor
{
    private HashSet<Expression> parameterlessExpressions;
    public ParameterlessExpressionEvaluator(HashSet<Expression> parameterlessExpressions)
    {
        this.parameterlessExpressions = parameterlessExpressions;
    }
    public override Expression Visit(Expression node)
    {
        if (parameterlessExpressions.Contains(node))
            return Evaluate(node);
        else
            return base.Visit(node);
    }

    private Expression Evaluate(Expression node)
    {
        if (node.NodeType == ExpressionType.Constant)
        {
            return node;
        }
        object value = Expression.Lambda(node).Compile().DynamicInvoke();
        return Expression.Constant(value, node.Type);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们只需要一个简单的方法来首先执行第一个搜索器,然后是第二个,并返回结果,并提供一个将结果转换为通用表达式的重载:

public static class ExpressionExtensions
{
    public static Expression Simplify(this Expression expression)
    {
        var searcher = new ParameterlessExpressionSearcher();
        searcher.Visit(expression);
        return new ParameterlessExpressionEvaluator(searcher.ParameterlessExpressions).Visit(expression);
    }

    public static Expression<T> Simplify<T>(this Expression<T> expression)
    {
        return (Expression<T>)Simplify((Expression)expression);
    }

    //all previously shown code goes here

}
Run Code Online (Sandbox Code Playgroud)

现在你可以写:

Expression<Func<Products, bool>> e = x => x.Id == i;
e = e.Simplify();
Console.WriteLine(e);
Run Code Online (Sandbox Code Playgroud)

它会打印:

"x => (x.Id == 9)"
Run Code Online (Sandbox Code Playgroud)

或者,如果您只想评估一个特定的表达式,并且您愿意首先更改编写表达式的方式以适应,则此答案显示了如何编写一种方法来指示应评估哪些子表达式,并仅评估这些表达式。如果您想评估某些事情而不是其他事情,这将很有用。

  • 我已经尝试解决这个问题几个小时了,你的解决方案来了。太感谢了。 (2认同)