# [C# 基础知识系列]专题十四:深入理解Lambda表达式
**引言:**
对于刚刚接触Lambda表达式的朋友们,可能会对Lambda表达式感到非常疑惑,它到底是个什么什么样的技术呢?以及它有什么好处和先进的地方呢?下面的介绍将会解除你这些疑惑。
**一、Lambda表达式的演变过程**
Lambda表达式其实大家可以理解为它是一个**匿名函数**(对于匿名函数的介绍大家可以参考我[这篇文章](http://www.cnblogs.com/zhili/archive/2012/12/01/anonymousmethod.html "匿名函数")), Lambda表达式可以包含表达式和语句,并且可以用于**创建委托,以及C#编译器也能将它转换成表达式树。**
对于Lambda表达式中都会使用这个运算符——“=>”,它读成“goes to” ,该运算符的左边为输入参数,右边是表达式或者语句块,下面就看看Lambda表达式是如何来创建委托实例(代码同时也给出了Lambda表达式从匿名方法的演示过程,从而帮助大家更好的理解Lambda表达式是匿名函数的概念,只不过C#3 中提出的Lambda表达式比匿名函数的使用更加简洁和直观了,其实原理都是一样的, 编译器同样会把Lambda表达式编译成匿名函数,也就是一个名字的方法):
```
using System;
namespace Lambda表达式Demo
{
class Program
{
/// <summary>
/// Lambda 表达式使用演示
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
// Lambda表达式的演变过程
// 下面是C# 1中创建委托实例的代码
Func<string, int> delegatetest1 = new Func<string, int>(Callbackmethod);
// ↓
// C# 2中用匿名方法来创建委托实例,此时就不需要额外定义回调方法Callbackmethod
Func<string, int> delegatetest2 = delegate(string text)
{
return text.Length;
};
// ↓
// C# 3中使用Lambda表达式来创建委托实例
Func<string, int> delegatetest3 = (string text) => text.Length;
// ↓
// 可以省略参数类型string,把上面代码再简化为:
Func<string, int> delegatetest4 = (text) => text.Length;
// ↓
// 如果Lambda表达式只需一个参数,并且那个参数可以隐式指定类型时,
// 此时可以把圆括号也省略,简化为:
Func<string, int> delegatetest = text => text.Length;
// 调用委托
Console.WriteLine("使用Lambda表达式返回字符串的长度为: " + delegatetest("learning hard"));
Console.Read();
}
/// <summary>
/// 回调方法
/// 如果使用了Lambda表达式和匿名函数,此方法就不需要额外定义
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
private static int Callbackmethod(string text)
{
return text.Length;
}
}
}
```
运行结果为:
![](https://box.kancloud.cn/2016-01-23_56a2eb2cb2412.jpg)
上面代码中都有详细的演变过程,这里就不多解释了,希望通过这部分之后,大家可以对Lambda表达式有进一步的理解,其实Lambda表达式就是匿名方法,其中使用Lambda表达式来创建委托实例,我们却没有指出创建的委托类型,其中编译器会帮助我们去推断委托类型,从而简化我们创建委托类型所需要的代码,从而更加简洁,所以Lambda表达式可以总结为——**它是在匿名方法的基础上,再进一步地简化了创建委托实例所需要的代码。**
**二、Lambda表达式的使用**
为了帮助大家更好的理解Lambda表达式,下面演示下用Lambda表达式来记录事件(代码中Lambda运算符的右边调用了一个回调方法ReportEvent()):
```
using System;
using System.Windows.Forms;
namespace Lambda表达式来记录事件Demo
{
class Program
{
static void Main(string[] args)
{
// 新建一个button实例
Button button1 = new Button() { Text ="点击我"};
// C# 2中使用匿名方法来订阅事件
//button1.Click+=delegate (object sender,EventArgs e)
//{
// ReportEvent("Click事件", sender, e);
//};
//button1.KeyPress += delegate (object sender, KeyPressEventArgs e)
//{
// ReportEvent("KeyPress事件,即键盘按下事件", sender, e);
//};
// C# 3Lambda表达式方式来订阅事件
// 与上面使用匿名方法来订阅事件是不是看出简单了很多,并且也直观了
button1.Click += (sender, e) => ReportEvent("Click事件", sender, e);
button1.KeyPress += (sender, e) => ReportEvent("KeyPress事件,即键盘按下事件", sender, e);
// C# 3之前初始化对象时使用下面代码
//Form form = new Form();
//form.Name = "在控制台中创建的窗体";
//form.AutoSize = true;
//form.Controls.Add(button1);
// C# 3中使用对象初始化器
// 与上面代码的比较中,也可以看出使用对象初始化之后代码简化了很多
Form form = new Form { Name = "在控制台中创建的窗体", AutoSize = true, Controls = { button1 } };
// 运行窗体
Application.Run(form);
}
// 记录事件的回调方法
private static void ReportEvent(string title, object sender, EventArgs e)
{
Console.WriteLine("发生的事件为:{0}", title);
Console.WriteLine("发生事件的对象为:{0}", sender);
Console.WriteLine("发生事件参数为: {0}", e.GetType());
Console.WriteLine();
Console.WriteLine();
}
}
}
```
运行结果:
![](https://box.kancloud.cn/2016-01-23_56a2eb2cc411f.jpg)
从上面代码中可以看出,使用Lambda表达式之后代码确实简洁了很多,上面代码中都有详细的注释,这里就不解释了,大家可以查看代码中的注释来进行理解,并且代码中注释部分也列出了C# 3之前是如何实现这样的代码的, 这样有利于比较,从而帮助大家更好的认识到Lambda所带来的好处和进一步来理解Lambda表达式。
**三、表达式树**
上面指出Lambda表达式除了可以用来创建委托外,C#编译器还可以将他们转换成表达式树——用于表示Lambda表达式逻辑的一种数据结构,表达式树也可以称作表达式目录树,它将代码表示成一个对象树,而不是可执行的代码。对于刚接触哦表达式树的朋友肯定会问——为什么需要把Lambda表达式转化为表达式目录树呢?对于表达式树的提出主要是为后面Linq to SQL 做铺垫,一个Linq to SQL 的查询语句并不是在C#的程序中执行的,而是C#编译器把它转化为SQL 语句,然后再在数据库中执行。在我们使用Linq to SQL的时候都需要添加一个Linq to SQL的类,该类的扩展名dbml,该的作用就是帮助我们把Linq to SQL 的语句映射为SQL语句,然后再在数据库中执行SQL语句,把返回的结果再返回给一个IQueryable集合,所以Linq to SQL 也采用了通常的ORM(Object—Relationship—Mapping)来设计的,相当于是一个ORM框架,不过这个框架只能与微软的SQL server数据库进行映射,对于其他类型的数据库却不可以,然而很多其他开发人员却对此进行了一些扩展,扩展了对其他数据库的支持。前不久还在博客园中发布了开源的Linq框架的,名字为ELinq,其他它就是对Linq to SQL的一个扩展,使Linq语句可以映射到其他数据库的查询语句。
下面先看看如何把Lambda表达式转化为表达式目录树(**其中需要引入一个新的命名空间—— System.Linq.Expressions**):
```
using System;
// 引用额外的命名空间
using System.Linq.Expressions;
namespace 表达式树Demo
{
class Program
{
/// <summary>
/// 表达式树的演示
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
#region 将Lambda表达式转换为表达式树演示
// 将Lambda表达式转换为Express<T>的表达式树
// 此时express不是可执行的代码,它现在是一个表达式树的数据结构
Console.WriteLine("将Lambda表达式转化为表达式树的演示:");
Expression<Func<int, int, int>> expression = (a, b) => a + b;
// 获得表达式树的参数
Console.WriteLine("参数1: {0},参数2:{1}", expression.Parameters[0],expression.Parameters[1]);
// 既然叫做树,那肯定有左右节点
// 获取表达式树的主体部分
BinaryExpression body = (BinaryExpression)expression.Body;
// 左节点,每个节点本身就是一个表达式对象
ParameterExpression left = (ParameterExpression)body.Left;
// 右节点
ParameterExpression right = (ParameterExpression)body.Right;
Console.WriteLine("表达式主体为:");
Console.WriteLine(expression.Body);
Console.WriteLine("表达式树左节点为:{0}{4} 节点类型为:{1}{4}{4} 表达式右节点为:{2}{4} 节点类型为:{3}{4}", left.Name, left.NodeType, right.Name, right.NodeType,Environment.NewLine);
Console.Read();
#endregion
#region 把表达式树转化回可执行代码
// Compile方法生成Lambda表达式的委托
Console.WriteLine("按下Enter键进入将表达式树转换为Lambda表达式的委托演示:");
int result = expression.Compile()(2, 3);
Console.WriteLine("调用Lambda表达式委托结果为:" + result);
Console.ReadKey();
#endregion
}
}
}
```
运行结果:
![](https://box.kancloud.cn/2016-01-23_56a2eb2cd9a46.jpg)
上面代码首先把Lambda表达式转化为表达式树,下面这行代码就是把Lambda表达式转化为表达式树:
之后对于表达式树这种数据结构进行分析来获得该树中的主体和左右节点是什么,获得主体和左右节点的代码如下:
```
// 获取表达式树的主体部分
BinaryExpression body = (BinaryExpression)expression.Body;
// 左节点,每个节点本身就是一个表达式对象
ParameterExpression left = (ParameterExpression)body.Left;
// 右节点
ParameterExpression right = (ParameterExpression)body.Right;
```
从上面代码可以得出——树中的每个节点都是一个表达式(**ParameterExpression和BinaryExpression都是继承Expression的,所以左右节点都是表达式**),分析完表达式树之后,代码中还演示了如果把表达式树转化为可执行的代码,即转化为Lambda表达式的委托对象(**此时调用Expression的Compile()方法来转化为可执行代码**),通过调用委托来获得结果。
关于Lambda表达式树的更多信息还可以参看这篇博客:[http://www.cnblogs.com/tianfan/archive/2010/03/05/expression-tree-basics.html](http://www.cnblogs.com/tianfan/archive/2010/03/05/expression-tree-basics.html) (博主翻译的还可以)
**四、总结**
到这里本专题的内容也介绍的差不多了,希望通过本专题使一些之前对Lambda表达式感到疑惑的朋友们现在可以理解Lambda表达式,因为只有理解好Lambda表达式之后,对于Linq的学习就可以说是轻而易举了。
补充:
1.匿名函数不等于匿名方法,匿名函数包含了匿名方法和lambda表达式这两种概念。 匿名函数:{匿名方法,lambda表达式} lambda作为表达式,可以被C#编译器转换为委托,也可以被编译器转换为表达式树,匿名方法只能转换为委托。 两者的共通点是都能被编译器转换成为委托,lambda表达式能完成几乎所有匿名方法能完成的事。 作为委托和表达式树,两者在IL阶段表示就不一样了。作为委托的IL,在运行期间直接被CLR所执行,而作为表达式树,是不被CLR所直接执行,而是通过相应的Provider转换为所需要的东西,比如说可以转换为SQL,也可以转换为JAVA。(引自留言中浪雪朋友的意见)
本专题中演示源码:[http://files.cnblogs.com/zhili/Lambda%E8%A1%A8%E8%BE%BE%E5%BC%8FDemo.zip](http://files.cnblogs.com/zhili/Lambda%E8%A1%A8%E8%BE%BE%E5%BC%8FDemo.zip)
- C# 基础知识系列
- C# 基础知识系列 专题一:深入解析委托——C#中为什么要引入委托
- C# 基础知识系列 专题二:委托的本质论
- C# 基础知识系列 专题三:如何用委托包装多个方法——委托链
- C# 基础知识系列 专题四:事件揭秘
- C# 基础知识系列 专题五:当点击按钮时触发Click事件背后发生的事情
- C# 基础知识系列 专题六:泛型基础篇——为什么引入泛型
- C# 基础知识系列 专题七: 泛型深入理解(一)
- C# 基础知识系列 专题八: 深入理解泛型(二)
- C# 基础知识系列 专题九: 深入理解泛型可变性
- C#基础知识系列 专题十:全面解析可空类型
- C# 基础知识系列 专题十一:匿名方法解析
- C#基础知识系列 专题十二:迭代器
- C#基础知识 专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
- C# 基础知识系列 专题十四:深入理解Lambda表达式
- C# 基础知识系列 专题十五:全面解析扩展方法
- C# 基础知识系列 专题十六:Linq介绍
- C#基础知识系列 专题十七:深入理解动态类型
- 你必须知道的异步编程 C# 5.0 新特性——Async和Await使异步编程更简单
- 全面解析C#中参数传递
- C#基础知识系列 全面解析C#中静态与非静态
- C# 基础知识系列 C#中易混淆的知识点
- C#进阶系列
- C#进阶系列 专题一:深入解析深拷贝和浅拷贝
- C#进阶系列 专题二:你知道Dictionary查找速度为什么快吗?
- C# 开发技巧系列
- C# 开发技巧系列 使用C#操作Word和Excel程序
- C# 开发技巧系列 使用C#操作幻灯片
- C# 开发技巧系列 如何动态设置屏幕分辨率
- C# 开发技巧系列 C#如何实现图片查看器
- C# 开发技巧 如何防止程序多次运行
- C# 开发技巧 实现属于自己的截图工具
- C# 开发技巧 如何使不符合要求的元素等于离它最近的一个元素
- C# 线程处理系列
- C# 线程处理系列 专题一:线程基础
- C# 线程处理系列 专题二:线程池中的工作者线程
- C# 线程处理系列 专题三:线程池中的I/O线程
- C# 线程处理系列 专题四:线程同步
- C# 线程处理系列 专题五:线程同步——事件构造
- C# 线程处理系列 专题六:线程同步——信号量和互斥体
- C# 多线程处理系列专题七——对多线程的补充
- C#网络编程系列
- C# 网络编程系列 专题一:网络协议简介
- C# 网络编程系列 专题二:HTTP协议详解
- C# 网络编程系列 专题三:自定义Web服务器
- C# 网络编程系列 专题四:自定义Web浏览器
- C# 网络编程系列 专题五:TCP编程
- C# 网络编程系列 专题六:UDP编程
- C# 网络编程系列 专题七:UDP编程补充——UDP广播程序的实现
- C# 网络编程系列 专题八:P2P编程
- C# 网络编程系列 专题九:实现类似QQ的即时通信程序
- C# 网络编程系列 专题十:实现简单的邮件收发器
- C# 网络编程系列 专题十一:实现一个基于FTP协议的程序——文件上传下载器
- C# 网络编程系列 专题十二:实现一个简单的FTP服务器
- C# 互操作性入门系列
- C# 互操作性入门系列(一):C#中互操作性介绍
- C# 互操作性入门系列(二):使用平台调用调用Win32 函数
- C# 互操作性入门系列(三):平台调用中的数据封送处理
- C# 互操作性入门系列(四):在C# 中调用COM组件
- CLR
- 谈谈: String 和StringBuilder区别和选择
- 谈谈:程序集加载和反射
- 利用反射获得委托和事件以及创建委托实例和添加事件处理程序
- 谈谈:.Net中的序列化和反序列化
- C#设计模式
- UML类图符号 各种关系说明以及举例
- C#设计模式(1)——单例模式
- C#设计模式(2)——简单工厂模式
- C#设计模式(3)——工厂方法模式
- C#设计模式(4)——抽象工厂模式
- C#设计模式(5)——建造者模式(Builder Pattern)
- C#设计模式(6)——原型模式(Prototype Pattern)
- C#设计模式(7)——适配器模式(Adapter Pattern)
- C#设计模式(8)——桥接模式(Bridge Pattern)
- C#设计模式(9)——装饰者模式(Decorator Pattern)
- C#设计模式(10)——组合模式(Composite Pattern)
- C#设计模式(11)——外观模式(Facade Pattern)
- C#设计模式(12)——享元模式(Flyweight Pattern)
- C#设计模式(13)——代理模式(Proxy Pattern)
- C#设计模式(14)——模板方法模式(Template Method)
- C#设计模式(15)——命令模式(Command Pattern)
- C#设计模式(16)——迭代器模式(Iterator Pattern)
- C#设计模式(17)——观察者模式(Observer Pattern)
- C#设计模式(18)——中介者模式(Mediator Pattern)
- C#设计模式(19)——状态者模式(State Pattern)
- C#设计模式(20)——策略者模式(Stragety Pattern)
- C#设计模式(21)——责任链模式
- C#设计模式(22)——访问者模式(Vistor Pattern)
- C#设计模式(23)——备忘录模式(Memento Pattern)
- C#设计模式总结
- WPF快速入门系列
- WPF快速入门系列(1)——WPF布局概览
- WPF快速入门系列(2)——深入解析依赖属性
- WPF快速入门系列(3)——深入解析WPF事件机制
- WPF快速入门系列(4)——深入解析WPF绑定
- WPF快速入门系列(5)——深入解析WPF命令
- WPF快速入门系列(6)——WPF资源和样式
- WPF快速入门系列(7)——深入解析WPF模板
- WPF快速入门系列(8)——MVVM快速入门
- WPF快速入门系列(9)——WPF任务管理工具实现
- ASP.NET 开发
- ASP.NET 开发必备知识点(1):如何让Asp.net网站运行在自定义的Web服务器上
- ASP.NET 开发必备知识点(2):那些年追过的ASP.NET权限管理
- ASP.NET中实现回调
- 跟我一起学WCF
- 跟我一起学WCF(1)——MSMQ消息队列
- 跟我一起学WCF(2)——利用.NET Remoting技术开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(4)——第一个WCF程序
- 跟我一起学WCF(5)——深入解析服务契约 上篇
- 跟我一起学WCF(6)——深入解析服务契约 下篇
- 跟我一起学WCF(7)——WCF数据契约与序列化详解
- 跟我一起学WCF(8)——WCF中Session、实例管理详解
- 跟我一起学WCF(9)——WCF回调操作的实现
- 跟我一起学WCF(10)——WCF中事务处理
- 跟我一起学WCF(11)——WCF中队列服务详解
- 跟我一起学WCF(12)——WCF中Rest服务入门
- 跟我一起学WCF(13)——WCF系列总结
- .NET领域驱动设计实战系列
- .NET领域驱动设计实战系列 专题一:前期准备之EF CodeFirst
- .NET领域驱动设计实战系列 专题二:结合领域驱动设计的面向服务架构来搭建网上书店
- .NET领域驱动设计实战系列 专题三:前期准备之规约模式(Specification Pattern)
- .NET领域驱动设计实战系列 专题四:前期准备之工作单元模式(Unit Of Work)
- .NET领域驱动设计实战系列 专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
- .NET领域驱动设计实战系列 专题六:DDD实践案例:网上书店订单功能的实现
- .NET领域驱动设计实战系列 专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
- .NET领域驱动设计实战系列 专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现
- .NET领域驱动设计实战系列 专题九:DDD案例:网上书店AOP和站点地图的实现
- .NET领域驱动设计实战系列 专题十:DDD扩展内容:全面剖析CQRS模式实现
- .NET领域驱动设计实战系列 专题十一:.NET 领域驱动设计实战系列总结