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

时间:2018-12-07 20:17:30

标签: c# .net linq linq-expressions

我有这个方法和参数。

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”。

4 个答案:

答案 0 :(得分:3)

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

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

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

或者,如果您只想评估一个特定的表达式,并且愿意改变写该表达式的方式来适应,this answer展示了如何编写一种方法来指示哪个子-expressions应该被评估,并且仅评估那些表达式。如果您想评估某些事物而不是其他事物,那将很有用。

答案 1 :(得分:1)

.ToString()为我工作:

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

使用

拨打电话时
SomeMethod(x=>x.Id==9);

输出:

  

x =>(x.Id == 9)

答案 2 :(得分:0)

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

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

您可以尝试编写一个ExpressionVisitor,尝试找到这样的闭包并对其进行评估,但是我从来没有亲自尝试过。

答案 3 :(得分:0)

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

它可能看起来像下面的存根:

   AWS Instance ID | Public IP     |Key Name    Owner     Instance State

   i-9a02a66b      ,54.xx.xx.201,52.x.xxxx.160,52.x.xx.175,34.xx.xx.31

请注意VisitMember中的逻辑,该逻辑可以从闭包中提取值。

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

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