在C#中,我可以从方法而不是Lambda创建表达式树对象吗?

huangapple go评论71阅读模式
英文:

in C#, can I create Exression tree object from a method instead of lambda?

问题

I want to create an expression tree object that represents some condition to be used in EF Core Linq query Where clause.
Since this condition will be used in different queries I want to define it once and use it anywhere.
However, I also want to have this condition in a form of a method so that I can use it without Linq.

So my question is can I have method from which I can build expression tree object? I know expression tree can be built from the lambda when immediately assigned to it, but can I create expression tree from separately defined methods? Here is what I mean:

using System.Linq.Expressions;

internal class Program
{
    private static void Main(string[] args)
    {
        Console.WriteLine("Hello, World!");

        Expression<Func<int, bool>> expressionTree1 = (int x) => x % 2 == 0; // compiles

        var lambda1 = (int x) => x % 2 == 0;

        // following two don't compile with an error:
        //      Expression<Func<int, bool>> does not contain a constructor that takes 1 argument
        var expressionTree2 = new Expression<Func<int, bool>>(lambda1);
        var expressionTree3 = new Expression<Func<int, bool>>(IsEven);
    }

    private static bool IsEven(int x)
    {
        return x % 2 == 0;
    }
}

So I would like to somehow build Expression<Func<int, bool>> from method defined elsewhere such as IsEven above.

英文:

I want to create an expression tree object that represents some condition to be used in EF Core Linq query Where clause.
Since this condition will be used in different queries I want to define it once and use it anywhere.
However, I also want to have this condition in a form of a method so that I can use it without Linq.

So my question is can I have method from which I can build expression tree object? I know expression tree can be built from the lambda when immediately assigned to it, but can I create expression tree from separately defined methods? Here is what I mean:

using System.Linq.Expressions;

internal class Program
{
    private static void Main(string[] args)
    {
        Console.WriteLine(&quot;Hello, World!&quot;);

        Expression&lt;Func&lt;int, bool&gt;&gt; expressionTree1 = (int x) =&gt; x % 2 == 0; // compiles

        var lambda1 = (int x) =&gt; x % 2 == 0;

        // following two don&#39;t compile with an error:
        //      Expression&lt;Func&lt;int, bool&gt;&gt; does not contain a constructor that takes 1 argument
        var expressionTree2 = new Expression&lt;Func&lt;int, bool&gt;&gt;(lambda1);
        var expressionTree3 = new Expression&lt;Func&lt;int, bool&gt;&gt;(IsEven);
    }

    private static bool IsEven(int x)
    {
        return x % 2 == 0;
    }
}

So I would like to somehow build Expression&lt;Func&lt;int, bool&gt;&gt; from method defined elsewhere such as IsEven above.

答案1

得分: 3

I don't think you can, since expression trees can only contain a subset of what a method can contain.

你可能无法做到这一点,因为表达式树只能包含方法的子集。

How about declaring the method in terms of the expression tree, rather than the other way around? This should also achieve your goal of only writing the condition once.

不如考虑根据表达式树来声明方法,而不是反过来。这样也可以实现你的目标,只需编写一次条件。

static Expression<Func<int, bool>> expressionTree1 = (int x) => x % 2 == 0;

// only need to compile this once
// 只需要编译一次
static Func<int, bool> compiledExpressionTree1 = expressionTree1.Compile();

private static void Main(string[] args)
{
Console.WriteLine(IsEven(1)); // false
}

private static bool IsEven(int x)
{
return compiledExpressionTree1(x);
}

英文:

I don't think you can, since expression trees can only contain a subset of what a method can contain.

How about declaring the method in terms of the expression tree, rather than the other way around? This should also achieve your goal of only writing the condition once.

static Expression&lt;Func&lt;int, bool&gt;&gt; expressionTree1 = (int x) =&gt; x % 2 == 0;

// only need to compile this once
static Func&lt;int, bool&gt; compiledExpressionTree1 = expressionTree1.Compile();

private static void Main(string[] args)
{
    Console.WriteLine(IsEven(1)); // false
}

private static bool IsEven(int x)
{
    return compiledExpressionTree1(x);
}

答案2

得分: 1

当您从内联lambda表达式创建表达式(如expressionTree1)时,C#编译器会将C#代码转换为“Expression”对象;lambda方法本身不存在,它只是语法糖。这导致代码如下所示:

ParameterExpression paramX = Expression.Parameter(typeof(int), "x");
Expression isEven = Expression.Lambda(
    Expression.Equal(
        Expression.Modulo(
            paramX,
            Expression.Constant(2)
        ),
        Expression.Constant(0)
    ),
    paramX
);

您无法从方法中创建表达式,因为编译器无法获取方法的主体以进行转换。在您的示例中,代码位于同一文件中,但您可能希望在不同的文件或程序集中使用方法(您可能没有源代码)。您可以创建一个调用您的方法的表达式,但这不适用于EF Core,因为EF将表达式树转换为SQL查询。

ParameterExpression paramX = Expression.Parameter(typeof(int), "x");
Expression isEven = Expression.Lambda(
    Expression.Call(
        typeof(Program).GetMethod(nameof(IsEven), BindingFlags.Static | BindingFlags.NonPublic)!,
        paramX
    ),
    paramX
);

Sweeper的回答是解决方法,这个回答只是解释为什么要这样做。

英文:

When you create an expression from a lambda inline (like expressionTree1) the C# compiler coverts the C# code into Expression objects; the lambda method itself doesn't exist, it's just syntactic sugar. This results in code like:

ParameterExpression paramX = Expression.Parameter(typeof(int), &quot;x&quot;);
Expression isEven = Expression.Lambda(
    Expression.Equal(
        Expression.Modulo(
            paramX,
            Expression.Constant(2)
        ),
        Expression.Constant(0)
    ),
    paramX
);

You can't create an expression from a method because the compiler can't get the body of the method to convert it. In your example the the code is in the same file, but you might want to use a method in a different file or assembly (which you might not have the source code to).

You can create an expression that calls your method but this won't work with EF Core because EF converts the expression tree into an SQL query.

ParameterExpression paramX = Expression.Parameter(typeof(int), &quot;x&quot;);
Expression isEven = Expression.Lambda(
    Expression.Call(
        typeof(Program).GetMethod(nameof(IsEven), BindingFlags.Static | BindingFlags.NonPublic)!,
        paramX
    ),
    paramX
);

Sweeper's answer is is the solution, this answer is just to explain the why.

huangapple
  • 本文由 发表于 2023年5月11日 15:59:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/76225333.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定