Как я могу получить строку из выражения linq? - PullRequest
0 голосов
/ 07 декабря 2018

У меня есть этот метод и параметр.

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 голосов
/ 08 декабря 2018

Чтобы упростить выражение в целом, нужно скомпилировать его и выполнить скомпилированный делегат.Теперь вы не можете сделать это для любого выражения, в котором все еще есть выражения параметров, потому что вы еще не знаете, каким будет значение параметра (пока).Это означает, что у нас есть два фундаментальных шага: во-первых, определить, какие из подвыражений в нашем дереве действительно содержат параметр где-то в этом поддереве, а затем оценить все те, которые не имеют.

Так чтоПервый шаг - определить, какие выражения содержат параметр внутри них.Для этого мы создаем посетителя выражения, в котором есть поле, указывающее, находится ли оно в данный момент в поддереве, с параметром, который затем рекурсивно проверяет своих потомков, затем проверяет себя и затем объединяет результаты, добавляя все выражения без параметров в коллекцию, в то время какпо пути.

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

В качестве альтернативы, если вы просто хотите оценить одно конкретное выражение, ивы готовы изменить способ написания выражения в первую очередь для приспособления, этот ответ показывает, как вы можете написать метод для указания того, какие подвыражения следует оценивать, и для оценки только этих выражений.Это было бы полезно, если вы хотите оценить некоторые вещи, а не другие.

0 голосов
/ 08 декабря 2018

В вашем примере ваш код работает правильно.Проблема в том, что переменная i не объявлена ​​как const.Выражение должно предполагать, что значение i может измениться до его вызова.Этот код дает ожидаемый результат: const int i = 9;

К сожалению, этот подход, вероятно, не будет работать для кэширования методов по той же причине.Ваш код также не может гарантировать, что i не изменится между временем объявления выражения и временем его оценки.

Вы можете попытаться написать ExpressionVisitor, который пытается найтитакие замыкания и оцените их, но я никогда не пробовал сам.

0 голосов
/ 08 декабря 2018

Как уже отмечали другие, вы можете получить некоторое сходство с исходным выражением, вызвав для него ToString (), но это работает только для очень простых реализаций и не будет хорошо работать с замыканиями.Компилятор c # делает много «магии», чтобы заставить выражения вроде замыканий работать в выражениях, и результат, который вы видите <<DisplayClass », является результатом этого.Вам нужно реализовать пользовательский посетитель, чтобы пройти через выражение и выписать c # (по сути, обратный компилятор), чтобы вернуться к оригиналу.</p>

Вероятно, это будет выглядеть примерно так:

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);
}
0 голосов
/ 07 декабря 2018

.ToString() у меня работает:

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

При звонке с

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

Выходы:

x => (x.Id == 9)

...