首页 > 解决方案 > 如何从 linq 表达式中获取字符串?

问题描述

我有这个方法和参数。

void SomeMethod(Expression<Func<Products, bool>> where)

我这样称呼这个方法;

int i = 9;
SomeMethod(x=>x.Id==i)

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

"x=>x.Id==9"

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

"x => (x.Id == value(isTakibi.WepApp.Controllers.HomeController+<>c__DisplayClass4_0).i)"

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

标签: c#.netlinqlinq-expressions

解决方案


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

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

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;
    }
}

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

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);
    }
}

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

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

}

现在你可以写:

Expression<Func<Products, bool>> e = x => x.Id == i;
e = e.Simplify();
Console.WriteLine(e);

它会打印:

"x => (x.Id == 9)"

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


推荐阅读