【问题标题】:Create a FieldExpression instead of ConstantExpression创建 FieldExpression 而不是 ConstantExpression
【发布时间】:2021-09-29 23:40:38
【问题描述】:

这有点难以解释,所以我从一个例子开始。

     class Program
    {
        static void Main(string[] args)
        {
            var lst = new List<Test>();
            var value = "John";
            var exp1 = lst.AsQueryable().Where(l => l.Name == "John").Expression as MethodCallExpression;
            var exp2 = lst.AsQueryable().Where(l => l.Name == value).Expression as MethodCallExpression;

            Console.WriteLine(exp1.Arguments.Last().ToString()); // l => (l.name == "John")
            Console.WriteLine(exp2.Arguments.Last().ToString()); // l => (l.name == value(test1.Program+<>c__DisplayClass0_0).value)
        }
    }
    class Test
    {
        public string Name { get; set; }
    }

名称值 (John) 在 exp1 示例中是 ConstantExpression,我们可以使用 Expression.Constant() 函数创建它,但我需要以某种方式创建第二个表达式 (exp2),该值不仅仅是一个常量并且有一个局部变量的引用。 FieldExpressionexp2 示例中。

主要目标是使用 Roslyn API 和表达式创建 exp2,我不知道应该如何准确地传递值来实现。

要创建exp1,我们可以这样做:

    var parameter = Expression.Parameter(typeof(Test), "l");
    var member = Expression.PropertyOrField( parameter , "Name");
    var valueExp = Expression.Constant(value);
    var exp1 = Expression.Equal(member, valueExp);

如何创建exp2 !?

【问题讨论】:

  • 你的用例是什么?你想用它做什么?
  • @Timo:检查一下:github.com/alirezanet/Gridify/issues/24 EntityFramework SQL 提供程序在我们使用ConstantExpression(最后两个 cmets)时有不同的行为

标签: c# lambda expression roslyn


【解决方案1】:

我发现使用反编译器查看 C# 如何实现此表达式很有帮助;

string value = "Foo";
Expression<Func<string,bool>> test = f => f == value;

Becomes;

    private sealed class <>c__DisplayClass0_0
    {
        public string value;
    }

    public void M()
    {
        <>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
        <>c__DisplayClass0_.value = "Foo";
        ParameterExpression parameterExpression = Expression.Parameter(typeof(string), "f");
        BinaryExpression body = Expression.Equal(parameterExpression, Expression.Field(Expression.Constant(<>c__DisplayClass0_, typeof(<>c__DisplayClass0_0)), FieldInfo.GetFieldFromHandle((RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/)));
        ParameterExpression[] array = new ParameterExpression[1];
        array[0] = parameterExpression;
        Expression<Func<string, bool>> expression = Expression.Lambda<Func<string, bool>>(body, array);
    }

如果您将FieldInfo.GetFieldFromHandle((RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/) 替换为通过反射获得的FieldInfo,您应该有一些可以编译的东西。

可以看到C#生成了一个新的类,这样局部变量就可以存放在那里而不是在栈上。

这个类的实例作为常量传递到表达式中,然后访问该类的字段。

我还发现让 C# 创建一个模板表达式,然后定义一个 ExpressionVisitor 以将该表达式的一小部分与其他内容交换是很有用的。

例如,您可以编写一个ExpressionVisitor,它将上述表达式的Body 中参数f 的每个实例与l =&gt; l.NameBody 交换,以创建一个新的lambda。无需担心 value 参数是如何提供的。

【讨论】:

  • 谢谢 Jeremy 不错的收获。这对我来说是一个性能优化更改,所以问题是如何在没有反射开销的情况下实现这一点!
  • 由于常量类引用,您需要在每次调用时重建大部分表达式。尽管您可以静态缓存反射元数据。我不会太担心构建表达式树本身的成本。如果您“通常”调用.Where(...),则表达式树每次都会构建。
猜你喜欢
  • 1970-01-01
  • 2011-07-30
  • 2019-06-18
  • 2013-09-24
  • 2016-08-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-03
相关资源
最近更新 更多