首页 > 解决方案 > 创建 FieldExpression 而不是 ConstantExpression

问题描述

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

     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)ConstantExpressionexp1示例中是一个,我们可以使用Expression.Constant()函数创建它,但我需要以某种方式创建第二个表达式(exp2),该值不仅仅是一个常量,并且具有对局部变量的引用。该值为FieldExpressionInexp2示例。

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

要创建,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!?

标签: c#lambdaexpressionroslyn

解决方案


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

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

变成;

    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);
    }

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

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

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

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

例如,您可以编写一个将上述表达式中的每个参数实例与ofExpressionVisitor交换以创建一个新的 lambda。无需担心 value 参数是如何提供的。fBodyBodyl => l.Name


推荐阅读