繁体   English   中英

如何从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”。

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

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

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

或者,如果您只想评估一个特定的表达式,并且您愿意在第一时间更改您编写表达式的方式,那么这个答案将说明如何编写一个方法来指示应该评估哪些子表达式,并评估那些表达。 如果你想评估一些事情而不是其他事情,这将是有用的。

.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#编译器做了很多“魔术”来使闭包在表达式中起作用,而你所看到的“<> 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);
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM