Разрешить ссылки выражений на фактические значения - PullRequest
0 голосов
/ 07 мая 2020

Я хочу регистрировать фактические значения в выражении, а не ссылки на свойства / поля / константы, используемые в выражении. У меня есть скрипка: https://dotnetfiddle.net/7SNxAq код которой (для потомков):

using System;
using System.Linq.Expressions;

public class Program
{
    public static void Main()
    {
        var input = new Foo
        {
            Shape = "Sphere",
            SizeType = SizeType.Small
        };

        Expression<Func<Foo, bool>> expression = f =>
            f.Hue == Constants.Hues.Red &&
            f.Shape == input.Shape &&
            f.Size == input.SizeType.ToString() &&
            !f.IsDeleted;

        Console.WriteLine(expression);
    }
}

internal class Foo
{
    public string Hue { get; set; }
    public string Shape { get; set; }
    public bool IsDeleted { get; set; }
    public string Size { get; set; }
    public SizeType SizeType { get; set; }
}

internal enum SizeType
{
    Small, Medium, Large
}

internal class Constants
{
    public class Hues
    {
        public static string Red = "#f00";
    }
}

Результат Console.WriteLine(expression):

f => ((((f.Hue == Hues.Red) AndAlso (f.Shape == value(Program+<>c__DisplayClass0_0).input.Shape)) AndAlso (f.Size == value(Program+<>c__DisplayClass0_0).input.SizeType.ToString())) AndAlso Not(f.IsDeleted))

Но я бы хотел увидеть что-то вроде этого

f => ((((f.Hue == Hues.Red) AndAlso (f.Shape == "Sphere")) AndAlso (f.Size == "Small")) AndAlso Not(f.IsDeleted))

Можно ли это сделать, или мне не хватает смысла выражений?

1 Ответ

1 голос
/ 08 мая 2020

Итак, что необходимо, так это избавиться от замыканий. Для этого необходимо переписать дерево выражений (будьте осторожны, не все случаи учитываются в посетителе Expression):

public static void Main()
{
    // ...

    var updated = (Expression<Func<Foo, bool>>)
        new ClosureResolver().Visit(expression);

    // Outputs:
    // f => ((((f.Hue == Hues.Red) AndAlso (f.Shape == "Sphere")) AndAlso (f.Size == "Small")) AndAlso Not(f.IsDeleted))
    Console.WriteLine(updated);
}


public class ClosureResolver : ExpressionVisitor
{
    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        if (node.Arguments.Count == 0)
        {
            var objExpr = Visit(node.Object);

            if (objExpr is ConstantExpression objConstExpr)
            {
                var res = node.Method.Invoke(objConstExpr.Value, new object[0]);
                return Expression.Constant(res);
            }
        }

        return base.VisitMethodCall(node);
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        var childExpr = Visit(node.Expression);

        if (childExpr is ConstantExpression constExpr)
        {
            if (node.Member is FieldInfo field)
            {
                var constVal = field.GetValue(constExpr.Value);
                return Expression.Constant(constVal);
            }
            else if (node.Member is PropertyInfo prop)
            {
                var constVal = prop.GetValue(constExpr.Value);
                return Expression.Constant(constVal);
            }
        }

        return base.VisitMember(node);
    }
}

После того, как дерево выражений будет переписано - оно больше не будет иметь замыканий, поэтому он может вести себя иначе, если был изменен input:

static void ClosureDemo()
{
    var input = new Foo { Shape = "Sphere" };

    Expression<Func<Foo, bool>> expr = f =>
        f.Shape == input.Shape;

    var updated = (Expression<Func<Foo, bool>>) 
        new ClosureResolver().Visit(expr);

    var fn = expr.Compile();
    var updatedFn = updated.Compile();


    Console.WriteLine(fn(input)); // True
    Console.WriteLine(updatedFn(input)); // True

    input.Shape = "Cube";

    Console.WriteLine(fn(input)); // True
    Console.WriteLine(updatedFn(input)); // False
}
...