C# 表达式树 (Expression Trees) 详解

若 lambda 表达式被分配给 Expression<TDelegate> 类型的变量,则编译器可以发射代码以创建表示该 lambda 表达式的表达式树。C# 编译器只能从表达式 lambda (或单行 lambda)生成表达式树。
首页 新闻资讯 行业资讯 C# 表达式树 (Expression Trees) 详解

表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和 x < y 这样的二元运算等。你可以对表达式树中的代码进行编辑和运算。这样能够动态修改可执行代码、在不同数据库中执行 LINQ 查询以及创建动态查询。表达式树还能用于动态语言运行时 (DLR) 以提供动态语言和 .NET Framework 之间的互操作性。

Lambda 表达式创建表达式树

若 lambda 表达式被分配给 Expression<TDelegate> 类型的变量,则编译器可以发射代码以创建表示该 lambda 表达式的表达式树。C# 编译器只能从表达式 lambda (或单行 lambda)生成表达式树。

下列代码示例使用关键字 Expression 创建表示 lambda 表达式:

usingSystem.Linq.Expressions;namespace AppExpressionTrees
{
    internal class Program
    {
        static void Main(string[]args){
            Expression<Action<int>>actionExpression=n=>Console.WriteLine(n);Expression<Func<int,bool>>funcExpression1=(n)=>n<0;Expression<Func<int,int,bool>>funcExpression2=(n,m)=>n-m==0;Console.WriteLine(actionExpression);Console.WriteLine(funcExpression1);Console.WriteLine(funcExpression2);}
    }
}

图片图片

API 创建表达式树

通过 API 创建表达式树需要使用 Expression 类。下列代码示例展示如何通过 API 创建表示 lambda 表达式 num => num == 0

usingSystem;usingSystem.Linq.Expressions;class Program
{
    static void Main(){// 通过 Expression 类创建表达式树// lambda:num => num == 0ParameterExpression pExpression=Expression.Parameter(typeof(int));// 参数:numConstantExpression cExpression=Expression.Constant(0);// 常量:0BinaryExpression bExpression=Expression.MakeBinary(ExpressionType.Equal,pExpression,cExpression);// 表达式:num == 0Expression<Func<int,bool>>lambda=Expression.Lambda<Func<int,bool>>(bExpression,pExpression);// lambda 表达式:num => num == 0Console.WriteLine(lambda);}
}

图片图片

另一个例子:创建一个简单的加法表达式

usingSystem.Linq.Expressions;namespace AppExpressionTrees
{
    internal class Program
    {
        static void Main(string[]args){// 创建表达式树:num1 + num2ParameterExpression num1=Expression.Parameter(typeof(int),"num1");ParameterExpression num2=Expression.Parameter(typeof(int),"num2");BinaryExpression addExpression=Expression.Add(num1,num2);Expression<Func<int,int,int>>lambda=Expression.Lambda<Func<int,int,int>>(addExpression,num1,num2);Console.WriteLine(lambda);Console.WriteLine(lambda.Compile().Invoke(1,2));}
    }
}

图片图片

解析表达式树

下列代码示例展示如何分解表示 lambda 表达式 num => num == 0 的表达式树:

usingSystem;usingSystem.Linq.Expressions;class Program
{
    static void Main(){
        Expression<Func<int,bool>>funcExpression=num=>num==0;// 开始解析ParameterExpression pExpression=funcExpression.Parameters[0];// lambda 表达式参数BinaryExpression body=(BinaryExpression)funcExpression.Body;// lambda 表达式主体:num == 0Console.WriteLine($"解析:{pExpression.Name} => {body.Left} {body.NodeType} {body.Right}");}
}

另一个例子:解析加法表达式

usingSystem;usingSystem.Linq.Expressions;class Program
{
    static void Main(){
        Expression<Func<int,int,int>>funcExpression=(num1,num2)=>num1+num2;// 开始解析ParameterExpression pExpression1=funcExpression.Parameters[0];// 第一个参数ParameterExpression pExpression2=funcExpression.Parameters[1];// 第二个参数BinaryExpression body=(BinaryExpression)funcExpression.Body;// lambda 表达式主体:num1 + num2Console.WriteLine($"解析:{pExpression1.Name} + {pExpression2.Name} => {body.Left} {body.NodeType} {body.Right}");}
}

图片图片

表达式树的永久性

表达式树应具有永久性(类似字符串)。这意味着如果你想修改某个表达式树,则必须复制该表达式树然后替换其中的节点来创建一个新的表达式树。你可以使用表达式树访问者遍历现有表达式树。第七节介绍了如何修改表达式树。

编译表达式树

Expression<TDelegate> 类型提供了 Compile 方法以将表达式树表示的代码编译成可执行委托。

usingSystem;usingSystem.Linq.Expressions;class Program
{
    static void Main(){// 创建表达式树Expression<Func<string,int>>funcExpression=msg=>msg.Length;// 表达式树编译成委托var lambda=funcExpression.Compile();// 调用委托Console.WriteLine(lambda("Hello, World!"));// 语法简化Console.WriteLine(funcExpression.Compile()("Hello, World!"));}
}

图片图片

另一个例子:编译加法表达式

usingSystem;usingSystem.Linq.Expressions;class Program
{
    static void Main(){// 创建表达式树:num1 + num2ParameterExpression num1=Expression.Parameter(typeof(int),"num1");ParameterExpression num2=Expression.Parameter(typeof(int),"num2");BinaryExpression addExpression=Expression.Add(num1,num2);Expression<Func<int,int,int>>lambda=Expression.Lambda<Func<int,int,int>>(addExpression,num1,num2);// 编译表达式树var compiledLambda=lambda.Compile();// 调用委托Console.WriteLine(compiledLambda(3,4));// 输出:7}
}

图片图片

执行表达式树

执行表达式树可能会返回一个值,也可能仅执行一个操作(例如调用方法)。

usingSystem;usingSystem.Linq.Expressions;class Program
{
    static void Main(){
        constint n=1;constint m=2;// 待执行的表达式树BinaryExpression bExpression=Expression.Add(Expression.Constant(n),Expression.Constant(m));// 创建 lambda 表达式Expression<Func<int>>funcExpression=Expression.Lambda<Func<int>>(bExpression);// 编译 lambda 表达式Func<int>func=funcExpression.Compile();// 执行 lambda 表达式Console.WriteLine($"{n} + {m} = {func()}");}
}

另一个例子:执行字符串长度表达式

using System;
using System.Linq.Expressions;

class Program
{
    static void Main()
    {
        // 创建表达式树:msg => msg.Length
        ParameterExpression msg = Expression.Parameter(typeof(string), "msg");
        MemberExpression length = Expression.Property(msg, "Length");
        Expression<Func<string, int>> lambda = Expression.Lambda<Func<string, int>>(length, msg);

        // 编译表达式树
        var compiledLambda = lambda.Compile();

        // 执行 lambda 表达式
        Console.WriteLine(compiledLambda("Hello, World!")); // 输出:13
    }
}

图片图片

修改表达式树

该类继承 ExpressionVisitor 类,通过 Visit 方法间接调用 VisitBinary 方法将 != 替换成 ==。基类方法构造类似于传入的表达式树的节点,但这些节点将其子目录树替换为访问器递归生成的表达式树。

usingSystem;usingSystem.Linq.Expressions;class Program
{
    static void Main(){
        Expression<Func<int,bool>>funcExpression=num=>num==0;Console.WriteLine($"Source: {funcExpression}");var visitor=new NotEqualExpressionVisitor();var expression=visitor.Visit(funcExpression);Console.WriteLine($"Modify: {expression}");}

    publicclass NotEqualExpressionVisitor : ExpressionVisitor
    {publicExpression Visit(BinaryExpression node){returnVisitBinary(node);}

        protected override Expression VisitBinary(BinaryExpression node){returnnode.NodeType==ExpressionType.Equal
                ? Expression.MakeBinary(ExpressionType.NotEqual,node.Left,node.Right)// 重新弄个表达式:用 != 代替 ==: base.VisitBinary(node);}
    }
}

图片图片

另一个例子:将加法修改为乘法

usingSystem;usingSystem.Linq.Expressions;class Program
{
    static void Main(){
        Expression<Func<int,int,int>>funcExpression=(num1,num2)=>num1+num2;Console.WriteLine($"Source: {funcExpression}");var visitor=new MultiplyExpressionVisitor();var expression=visitor.Visit(funcExpression);Console.WriteLine($"Modify: {expression}");}

    publicclass MultiplyExpressionVisitor : ExpressionVisitor
    {publicExpression Visit(BinaryExpression node){returnVisitBinary(node);}

        protected override Expression VisitBinary(BinaryExpression node){returnnode.NodeType==ExpressionType.Add? Expression.MakeBinary(ExpressionType.Multiply,node.Left,node.Right)// 重新弄个表达式:用 * 代替 +: base.VisitBinary(node);}
    }
}

总结

以上内容详细介绍了 C# 中表达式树的相关知识,并提供了具体的示例代码,以帮助开发者更深入地理解这一重要概念。表达式树是 C# 中一种强大的工具,它允许开发者以树形结构表示代码逻辑,从而可以在运行时对代码进行分析、修改甚至动态生成。这种特性在许多场景下非常有用,例如构建动态查询(如 LINQ to SQL)、实现自定义解析器或优化运行时性能。

希望这些内容能够为你提供清晰的指导,帮助你更好地理解和使用表达式树。无论你是初学者还是有一定经验的开发者,都可以从中获得启发,并进一步探索表达式树在各种复杂场景中的潜力。通过不断实践和尝试,相信你会更加熟练地运用这一强大工具,提升代码的灵活性和效率。

47    2025-03-06 08:16:08    lambda 表达式 变量