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

How can i get a string from linq expression?

我有这个方法和参数

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".

.ToString() 适合我:

void SomeMethod(Expression<Func<Product, bool>> where)
{
    Console.WriteLine(where.ToString());
}

使用

调用时
SomeMethod(x=>x.Id==9);

输出:

x => (x.Id == 9)

在您的示例中,您的代码正在做正确的事情。问题是变量 i 没有声明为 const。该表达式必须假定 i 的值在被调用之前可能会发生变化。此代码为您提供了您期望的结果:const int i = 9;

不幸的是,出于同样的原因,这种方法可能不适用于方法缓存。您的代码也无法确保 i 在声明表达式和计算表达式之间不会发生变化。

您可以尝试编写一个 ExpressionVisitor 来尝试找到这样的闭包并对其求值,但我自己从未尝试过。

正如其他人指出的那样,您可以通过对其调用 ToString() 来获得与原始表达式的一些相似之处,但这仅适用于非常简单的实现,并且不适用于闭包。 C# 编译器做了很多 "magic" 来使诸如闭包之类的东西在表达式中工作,而你看到的“<>DisplayClass”就是它的结果。您需要实现一个自定义访问者来遍历表达式并写出 c#(本质上是一个反向编译器)以返回原始内容。

它可能类似于以下存根:

public sealed class ExpressionWriterVisitor : ExpressionVisitor
{
  private TextWriter _writer;

  public ExpressionWriterVisitor(TextWriter writer)
  {
    _writer = writer;
  }

  protected override Expression VisitParameter(ParameterExpression node)
  {
    _writer.Write(node.Name);
    return node;
  }

  protected override Expression VisitLambda<T>(Expression<T> node)
  {
    _writer.Write('(');
    _writer.Write(string.Join(',', node.Parameters.Select(param => param.Name)));
    _writer.Write(')');
    _writer.Write("=>");

    Visit(node.Body);

    return node;
  }

  protected override Expression VisitConditional(ConditionalExpression node)
  {
    Visit(node.Test);

    _writer.Write('?');

    Visit(node.IfTrue);

    _writer.Write(':');

    Visit(node.IfFalse);

    return node;
  }

  protected override Expression VisitBinary(BinaryExpression node)
  {
    Visit(node.Left);

    _writer.Write(GetOperator(node.NodeType));

    Visit(node.Right);

    return node;
  }

  protected override Expression VisitMember(MemberExpression node)
  {
    // Closures are represented as a constant object with fields representing each closed over value.
    // This gets and prints the value of that closure.

    if (node.Member is FieldInfo fieldInfo && node.Expression is ConstantExpression constExpr)
    {
      WriteConstantValue(fieldInfo.GetValue(constExpr.Value));
    }
    else
    {
      Visit(node.Expression);
      _writer.Write('.');
      _writer.Write(node.Member.Name);
    }
    return node;
  }

  protected override Expression VisitConstant(ConstantExpression node)
  {
    WriteConstantValue(node.Value);

    return node;
  }

  private void WriteConstantValue(object obj)
  {
    switch (obj)
    {
      case string str:
        _writer.Write('"');
        _writer.Write(str);
        _writer.Write('"');
        break;
      default:
        _writer.Write(obj);
        break;
    }
  }

  private static string GetOperator(ExpressionType type)
  {
    switch (type)
    {
      case ExpressionType.Equal:
        return "==";
      case ExpressionType.Not:
        return "!";
      case ExpressionType.NotEqual:
        return "!==";
      case ExpressionType.GreaterThan:
        return ">";
      case ExpressionType.GreaterThanOrEqual:
        return ">=";
      case ExpressionType.LessThan:
        return "<";
      case ExpressionType.LessThanOrEqual:
        return "<=";
      case ExpressionType.Or:
        return "|";
      case ExpressionType.OrElse:
        return "||";
      case ExpressionType.And:
        return "&";
      case ExpressionType.AndAlso:
        return "&&";
      case ExpressionType.Add:
        return "+";
      case ExpressionType.AddAssign:
        return "+=";
      case ExpressionType.Subtract:
        return "-";
      case ExpressionType.SubtractAssign:
        return "-=";
      default:
        return "???";
    }
  }
}

在 VisitMember 中注意,它有一些从闭包中提取值的逻辑。

这将打印出 "(x)=>x.Id==9":

static void Main(string[] args)
{
  var i = 9;
  Expression<Func<Product, bool>> where = x => x.Id == i;
  new ExpressionWriterVisitor(Console.Out).Visit(where);
}

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

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

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

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

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)"

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