Lambda表达式简介
Lambda表达式可以理解为一个匿名方法,它可以包含表达式和语句,并且用于创建委托或转换为表达式树。在使用Lambda表达式时,都会使用“=>”运算符(读作“goes to”),该运算符的左边时匿名方法的输入参数,右边是表达式或语句块。
Lambda表达式的演变过程
大家可以认为匿名方法就是Lambda表达式的“前世”,而Lambda则是匿名方法的投胎转世,种种因果都与Lambda表达式的演变过程有关。下面代码演示Lambda表达式从匿名方法一路演变而来的过程。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApp { class Program { static void Main(string[] args) { //Lambda表达式的演变过程 //下面是C# 1.0中创建委托实例的代码 Func<string, int> delegateTest_1 = new Func<string, int>(Callback); //C# 2.0中用匿名方法来创建委托实例,此时就不需要去额外定义回调方法Callback了 Func<string, int> delegateTest_2 = delegate (string text) { return text.Length; }; //C# 3.0中使用Lambda表达式创建委托实例 Func<string, int> delegateTest_3 = (string text) => text.Length; //可以省略参数类型string,从而将代码再次简化 Func<string, int> delegateTest_4 = (text) => text.Length; //再次简化,此时圆括号()也可以省略掉 Func<string, int> delegateTest_5 = text => text.Length; //调用委托 Console.WriteLine("字符串长度为:{0}",delegateTest_5("你好,世界!")); Console.ReadKey(); } //回调方法 private static int Callback(string text) { return text.Length; } } }
以上代码演示了Lambda表达式的如何由匿名方法演变而来的过程,由此看出Lambda表达式十分简洁。
Lambda表达式的使用
在实际开发过程中,委托的用途莫过于订阅事件了,为了加深大家对Lambda表达式的理解,这里选择演示用Lambda表达式去订阅事件。
下面给出的是C# 3.0之前的订阅代码,以形成对比,代码演示如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; //添加对System.Windows.Forms.dll的引用并加入命名空间 using System.Windows.Forms; namespace ConsoleApp { class Program { static void Main(string[] args) { Button btn = new Button(); btn.Text = "按钮"; //C# 2.0中使用匿名方法来订阅事件 btn.Click += delegate (object sender, EventArgs e) { ReportEvent("Click事件",sender,e); }; btn.KeyPress += delegate (object sender, KeyPressEventArgs e) { ReportEvent("KeyPress事件", sender, e); }; //在C# 3.0之前,初始化对象会使用以下代码 Form frm = new Form(); frm.Text = "控制台中创建出来的窗体"; frm.AutoSize = true; frm.Controls.Add(btn); //运行窗体 Application.Run(frm); } //记录事件的回调方法 private static void ReportEvent(string title,object sender,EventArgs e) { Console.WriteLine("发生事件:{0}",title); Console.WriteLine("发生事件对象:{0}",sender); Console.WriteLine("发生事件参数:{0}\n\n",e.GetType()); } } }
以上是C# 3.0之前的实现方式,运行结果如下所示:
看完了C# 3.0之前的实现代码,我们再来用C# 3.0特性实现同样的效果,C# 3.0的实现代码如下所示:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; //添加对System.Windows.Forms.dll的引用并加入命名空间 using System.Windows.Forms; namespace ConsoleApp { class Program { static void Main(string[] args) { Button btn = new Button() { Text = "按钮" }; //C# 3.0采用Lambda表达式的方式来订阅事件 btn.Click += (sender, e) => ReportEvent("Click事件", sender, e); btn.KeyPress += (sender, e) => ReportEvent("KeyPress事件", sender, e); ; //在C# 3.0中使用对象集合初始化器 Form frm = new Form() { Text = "控制台中创建出来的窗体", AutoSize = true, Controls = { btn } }; //运行窗体 Application.Run(frm); } //记录事件的回调方法 private static void ReportEvent(string title,object sender,EventArgs e) { Console.WriteLine("发生事件:{0}",title); Console.WriteLine("发生事件对象:{0}",sender); Console.WriteLine("发生事件参数:{0}\n\n",e.GetType()); } } }
以上代码可以看出,使用C# 3.0的对象集合初始化器和Lambda表达式后,代码确实简洁不少。这里,委托可以用Lambda表达式来实例化,去除了多余大括号代码。Lambda表达式的使用可以明显减少代码的书写量,从而有利于开发人员更好的维护代码,理清程序的结构。
表达式树
Lambda表达式除了可以用来创建委托外,还可以转换成表达式树。表达式树(或称“表达式目录树”)是用来表示Lambda表达式逻辑的一种数据结构,它将代码表示成一个对象树,而非可执行的代码。
动态构造一个表达式树
代码示例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; //引入Expressions<TDelegate>类的命名空间 using System.Linq.Expressions; namespace ConsoleApp { class Program { static void Main(string[] args) { // 构造“a+b”的表达式结构 //表达式参数 //ParameterExpression 表示一个命名的参数表达式。 //Expression 提供一种基类,表示表达式树节点的类派生自该基类。 它还包含用来创建各种节点类型的 static(在 Visual Basic 中为 Shared)工厂方法。这是一个 abstract 类 //Expression.Parameter(Type type, string name) 创建一个 ParameterExpression 节点,该节点可用于标识表达式树中的参数或变量。 //参数: //type: 参数或变量的类型。 name:仅用于调试或打印目的的参数或变量的名称。 ParameterExpression a = Expression.Parameter(typeof(int),"a"); ParameterExpression b = Expression.Parameter(typeof(int), "b"); //表达式树的主体部分 //BinaryExpression 表示具有二进制运算符的表达式。 //Expression.Add(Expression left, Expression right) 创建一个表示不进行溢出检查的算术加法运算的 BinaryExpression。 BinaryExpression be = Expression.Add(a,b); //构造表达式树 //Expression<> 将强类型化的 Lambda 表达式表示为表达式树形式的数据结构。 此类不能被继承。 //Expression.Lambda<>() 创建一个在编译时委托类型已知的 Expressions<TDelegate> Expression<Func<int, int, int>> expressionTree = Expression.Lambda<Func<int, int, int>>(be,a,b); //分析树结构,获取表达式树的主体部分 BinaryExpression body = (BinaryExpression)expressionTree.Body; //左节点,每一个节点本身就是一个表达式对象 ParameterExpression left = (ParameterExpression)body.Left; //右节点 ParameterExpression right = (ParameterExpression)body.Right; Console.WriteLine("表达式结构为:{0}",expressionTree.Body); Console.WriteLine(); Console.WriteLine("表达式左节点为:{0},节点类型为:{1}",left.Name,left.Type); Console.WriteLine(); Console.WriteLine("表达式右节点为:{0},节点类型为:{1}", right.Name, right.Type); Console.ReadKey(); } } }
以上代码演示了通过一个表达式动态地构造表达式树对象,然后输出表达式树的结构、主体、和左右节点的过程,运行结果如下所示:
前面代码所构造的表达式树结构可以用下图来更形象地表示,由图可以看出表达式树也是一种树形数据结构,该数据结构可以很好地描述Lambda表达式的逻辑。
通过Lambda表达式来构造表达式树
前面代码演示了动态地构造表达式树的方法,除此之外,还可以直接使用Lambda表达式来构造表达式树,具体构造过程如下所示:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; //引入Expressions<TDelegate>类的命名空间 using System.Linq.Expressions; namespace ConsoleApp { class Program { static void Main(string[] args) { // 构造“a+b”的表达式结构 //将Lambda表达式构造成表达式树 Expression<Func<int, int, int>> expressionTree = (a, b) => a + b; //获得表达式树参数 Console.WriteLine("参数1:{0},参数2:{1}",expressionTree.Parameters[0],expressionTree.Parameters[1]); Console.WriteLine(); //获取表达式树的主体部分 BinaryExpression body = (BinaryExpression)expressionTree.Body; //左节点,每一个节点本身就是一个表达式对象 ParameterExpression left = (ParameterExpression)body.Left; //右节点 ParameterExpression right = (ParameterExpression)body.Right; Console.WriteLine("表达式结构为:{0}",expressionTree.Body); Console.WriteLine(); Console.WriteLine("表达式左节点为:{0},节点类型为:{1}",left.Name,left.Type); Console.WriteLine(); Console.WriteLine("表达式右节点为:{0},节点类型为:{1}", right.Name, right.Type); Console.ReadKey(); } } }
从以上代码可以看出,通过Lambda表达式来构造表达式树的过程非常简单,只需要把Lambda表达式赋给一个表达式树变量就可以了。构造完一个表达式树后,由于表达式对象并不是可执行代码,它只是一个树形数据结构,所以需要在代码中再解析树结果,分别获得树的参数和左右节点,然后输出,运行结果如下所示:
如何把表达式树转换成可执行代码
看完前面的代码,你肯定会问:“表达式树是一种树形数据结构,但最终还是需要得到代码的执行结果的,有没有一种方式可以把表达式树转换成可执行代码,然后输出执行结果呢?”,下面就用代码来解释这个问题。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; //引入Expressions<TDelegate>类的命名空间 using System.Linq.Expressions; namespace ConsoleApp { class Program { static void Main(string[] args) { //将Lambda表达式构造成表达式树 Expression<Func<int, int, int>> expressionTree = (a, b) => a + b; //通过调用Compile()方法来 lambda 表达式的委托。 Func<int, int, int> del = expressionTree.Compile(); //调用委托实例,获得结果 int result = del(5,10); //result为15 Console.WriteLine("5 + 15 的和为:{0}", result); Console.ReadKey(); } } }
以上代码通过Expressions<TDelegate>类的Compile()方法将表达式树编译成委托实例,然后通过委托调用的方式得到两个数的和。
文章评论