【问题标题】:Resolve expression references to actual values将表达式引用解析为实际值
【发布时间】:2020-05-07 15:05:18
【问题描述】:

我想在表达式中记录实际值,而不是对表达式中使用的属性/字段/常量的引用。我在这里有一个小提琴: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))

这可以做到吗,还是我错过了表达的重点?

【问题讨论】:

  • 你的表达即将结束input。为什么不传入第二个Foo 并在表达式中使用它:Expression&lt;Func&lt;Foo, Foo, bool&gt;&gt;
  • 感谢@Sean 的提示,但这个人为的例子最接近阅读的代码,在这里分享太多了。它也不会“扩展”或“解析”对其值的引用。

标签: c# .net lambda expression


【解决方案1】:

所以我们需要摆脱闭包。为此,需要重写表达式树(请注意,表达式访问者中不会考虑所有情况):

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
}

【讨论】:

  • 这就是我认为的问题所在。涵盖所有情况是一项艰巨的任务。即使在这个简单的例子中我们也有问题:Run-time exception (line -1): Attempt by method 'ClosureResolver.VisitMember(System.Linq.Expressions.MemberExpression)' to access field 'Program+&lt;&gt;c__DisplayClass0_0.input' failed.
  • 感谢您的反馈。我正在针对 .NET Core 3.1 进行测试 - 示例在那里工作:dotnetfiddle.net/NapHBB 我还能够在本地使用 .NET Framework 4.7 运行示例,但不能在 DotNetFiddle 中运行 - 一定存在阻止执行该逻辑的安全配置问题。
  • 请在 GitHub 上找到我开始的项目:github.com/AndreyChechel/Explosuress 现在逻辑处理更多情况,包括方法参数、索引器和静态字段/属性。 NuGet 包也可用。
猜你喜欢
  • 2019-09-20
  • 1970-01-01
  • 1970-01-01
  • 2016-04-22
  • 2010-11-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多