在C# 2.0中,通过方法组转换和匿名方法,使委托的实现得到了极大的简化。但是,匿名方法仍然有些臃肿,而且当代码中充满了匿名方法的时候,可读性可能就会受到影响。C# 3.0中出现的Lambda表达式在不牺牲可读性的前提下,进一步简化了委托。

LINQ的基本功能就是创建操作管道,以及这些操作需要的任何状态。这些操作表示了各种关于数据的逻辑,例如数据筛选,数据排序等等。通常这些操作都是用委托来表示。Lambda表达式是对LINQ数据操作的一种符合语言习惯的表示方式。

Lambda表达式不仅可以用来创建委托实例,C#编译器也能够将他们转换成表达式树。

下面我们就先看看Lambda表达式。

作为委托的Lambda表达式

Lambda表达式可以看作是C# 2.0的匿名方法的进一步演变,所以匿名方法能做的几乎一切事情都可以用Lambda表达式来完成(注意,匿名方法可以忽略参数,Lambda表达式不具备这个特性)。

跟匿名方法类似,Lambda表达式有特殊的转换规则:表达式的类型本身并非委托类型,但它可以通过隐式或显式的发那个是转换为一个委托实例。匿名函数这个术语同时涵盖了匿名方法和Lambda表达式。

下面看看使用Lambda表达式获得字符串长度的例子,通过Lambda将得到更见简洁、易读的代码:

C#
static void Main(string[] args)
{
//使用C# 2.0中的匿名方法获取字符串长度
Func<string, int> strLength = delegate(string str) { return str.Length; };
Console.WriteLine(strLength("Hello World!"));

//使用Lambda表达式
//(显式类型参数列表)=> {语句},lambda表达式最冗长版本
strLength = (string str) => { return str.Length; };
Console.WriteLine(strLength("Hello World!"));

//单一表达式作为主体
//(显式类型参数列表)=> 表达式
strLength = (string str) => str.Length;
Console.WriteLine(strLength("Hello World!"));

//隐式类型的参数列表
//(隐式类型参数列表)=> 表达式
strLength = (str) => str.Length;
Console.WriteLine(strLength("Hello World!"));

//单一参数的快捷语法
//参数名 => 表达式
strLength = str => str.Length;
Console.WriteLine(strLength("Hello World!"));
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static void Main(string[] args)
{
    //使用C# 2.0中的匿名方法获取字符串长度
    Func<string, int> strLength = delegate(string str) { return str.Length; };
    Console.WriteLine(strLength("Hello World!"));
 
    //使用Lambda表达式
    //(显式类型参数列表)=> {语句},lambda表达式最冗长版本
    strLength = (string str) => { return str.Length; };
    Console.WriteLine(strLength("Hello World!"));
 
    //单一表达式作为主体
    //(显式类型参数列表)=> 表达式
    strLength = (string str) => str.Length;
    Console.WriteLine(strLength("Hello World!"));
 
    //隐式类型的参数列表
    //(隐式类型参数列表)=> 表达式
    strLength = (str) => str.Length;
    Console.WriteLine(strLength("Hello World!"));
 
    //单一参数的快捷语法
    //参数名 => 表达式
    strLength = str => str.Length;
    Console.WriteLine(strLength("Hello World!"));
}

“=>”是C# 3.0新增的,告诉编译器我们正在使用Lambda表达式。”=>”可以读作”goes to”,所以例子中的Lambda表达式可以读作”str goes to str.Length”。从例子中还可以看到,根据Lambda使用的特殊情况,我们可以进一步简化Lambda表达式。

Lambda表达式大多数时候都是和一个返回非void的委托类型配合使用(例如Func<TResult>)。在C# 1.0中,委托一般用于事件,很少会返回什么结果。在LINQ中,委托通常被视为数据管道的一部分,接受输入并返回结果,或者判断某项是否符合当前的筛选 器等等。

Lambda表达式本质

通过ILSpy查看上面的例子,可以发现Lambda表达式就是匿名方法,是编译器帮我们进行了转换工作,使我们可以直接使用Lambda表达式来进一步简化创建委托实例的代码。

在List<T>中使用Lambda表达式

前面简单的介绍了什么是Lambda表达式,下面通过一个例子进一步了解Lambda表达式。

在前面的文章中,我们也提到了一下List<T>的方法,例如FindAll方法,参数是Predicate<T>类型的 委托,返回结果是一个筛选后的新列表;Foreach方法获取一个Action<T>类型的委托,然后对每个元素设置行为。下面就看看在 List<T>中使用Lambda表达式:

C#
public class Book
{
public string Name { get; set; }
public int Year { get; set; }
}

class Program
{
static void Main(string[] args)
{
var books = new List<Book>
{
new Book{Name="C# learning guide",Year=2005},
new Book{Name="C# step by step",Year=2005},
new Book{Name="Java learning guide",Year=2004},
new Book{Name="Java step by step",Year=2004},
new Book{Name="Python learning guide",Year=2003},
new Book{Name="C# in depth",Year=2012},
new Book{Name="Java in depth",Year=2014},
new Book{Name="Python in depth",Year=2013},
};

//创建一个委托实例来表示一个通用的操作
Action<Book> printer = book => Console.WriteLine("Name = {0}, Year = {1}", book.Name, book.Year);

books.ForEach(printer);

//使用Lambda表达式对List<T>进行筛选
books.FindAll(book => book.Year > 2010).ForEach(printer);

books.FindAll(book => book.Name.Contains("C#")).ForEach(printer);

//使用Lambda表达式对List<T>进行排序
books.Sort((book1, book2) => book1.Name.CompareTo(book2.Name));
books.ForEach(printer);

Console.Read();
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class Book
{
    public string Name { get; set; }
    public int Year { get; set; }
}
 
class Program
{
    static void Main(string[] args)
    {
        var books = new List<Book>
        {
            new Book{Name="C# learning guide",Year=2005},
            new Book{Name="C# step by step",Year=2005},
            new Book{Name="Java learning guide",Year=2004},
            new Book{Name="Java step by step",Year=2004},
            new Book{Name="Python learning guide",Year=2003},
            new Book{Name="C# in depth",Year=2012},
            new Book{Name="Java in depth",Year=2014},
            new Book{Name="Python in depth",Year=2013},
        };
 
        //创建一个委托实例来表示一个通用的操作
        Action<Book> printer = book => Console.WriteLine("Name = {0}, Year = {1}", book.Name, book.Year);
 
        books.ForEach(printer);
 
        //使用Lambda表达式对List<T>进行筛选
        books.FindAll(book => book.Year > 2010).ForEach(printer);
 
        books.FindAll(book => book.Name.Contains("C#")).ForEach(printer);
 
        //使用Lambda表达式对List<T>进行排序
        books.Sort((book1, book2) => book1.Name.CompareTo(book2.Name));
        books.ForEach(printer);
 
        Console.Read();
    }
}

从上面例子可以看到,当我们要经常使用一个操作的时候,我们最好创建一个委托实例,然后反复调用,而不是每次使用的时候都使用Lambda表达式(例如例子中的printer委托实例)。

相比C# 1.0中的委托或者C# 2.0的匿名函数,结合Lambda表达式,对List<T>中的数据操作变得简单,易读。

表达式树

表达式树也称表达式目录树,将代码以一种抽象的方式表示成一个对象树,树中每个节点本身都是一个表达式。表达式树不是可执行代码,它是一种数据结构。

下面我们看看怎么通过C#代码建立一个表达式树。

构建表达式树

System.Linq.Expressions命名空间中包含了代表表达式的各个类,所有类都从Expression派生,我们可以通过这些类中的静态方法来创建表达式类的实例。Expression类包括两个重要属性:

  • Type属性代表求值表达式的.NET类型,可以把它视为一个返回类型
  • NodeType属性返回所代表的表达式的类型

下面看一个构建表达式树的简单例子:

C#
Expression numA = Expression.Constant(6);
Console.WriteLine("NodeType: {0}, Type: {1}", numA.NodeType, numA.Type);
Expression numB = Expression.Constant(3);
Console.WriteLine("NodeType: {0}, Type: {1}", numB.NodeType, numB.Type);

BinaryExpression add = Expression.Add(numA, numB);
Console.WriteLine("NodeType: {0}, Type: {1}", add.NodeType, add.Type);

Console.WriteLine(add);
Console.Read();

1
2
3
4
5
6
7
8
9
10
Expression numA = Expression.Constant(6);
Console.WriteLine("NodeType: {0}, Type: {1}", numA.NodeType, numA.Type);
Expression numB = Expression.Constant(3);
Console.WriteLine("NodeType: {0}, Type: {1}", numB.NodeType, numB.Type);
 
BinaryExpression add = Expression.Add(numA, numB);
Console.WriteLine("NodeType: {0}, Type: {1}", add.NodeType, add.Type);
 
Console.WriteLine(add);
Console.Read();

代码的输出为:

通过例子可以看到,我们构建了一个(6+3)的表达式树,并且查看了各个节点的Type和NodeType属性。

Expression有很多派生类,有很多节点类型。例如,BinaryExpression就代表了具有两个操作树的任意操作。这正是NodeType属性重要的地方,它能区分由相同的类表示的不同种类的表达式。其他的节点类型就不介绍了,有兴趣可以参考MSDN。

对于上面的例子,可以用下图描述生成的表达式树,值得注意的是,”叶子”表达式在代码中是最先创建的,,表达式是自下而上构建的。表达式是不易变的,所有可以缓存和重用表达式。

将表达式编译成委托

LambdaExpression是从Expression派生的类型之一。泛型类型Expression<TDelegate>又是从LambdaExpress派生的。

Expression和Expression<TDelegate>的区别在于,泛型类以静态类型的方式标志了它是什么种类的表达式,也就是说,它确定了返回类型和参数。例如上面的加法例子,返回值是一个int类型,没有参数,所以我们可以使用签名Func<int>与之匹配,所以可以用Expression<Func<int>>以静态类型的方式来表示该表达式

这样做的目的在于,LambdaExpression有一个Compile方法,该方法能创建一个恰当类型的委托。 Expression<TDelegate>也有一个同名方法,该方法可以返回TDelegate类型的委托。获得了委托之后,我们就可以使 用普通委托实例调用的方式来执行这个表达式。

接着上面加法的例子,我们把上面的加法表达式树转换成委托,然后执行委托:

C#
Func<int> addDelegate = Expression.Lambda<Func<int>>(add).Compile();
Console.WriteLine(addDelegate());
1
2
Func<int> addDelegate = Expression.Lambda<Func<int>>(add).Compile();
Console.WriteLine(addDelegate());

从这个例子中我们看到怎么构建一个表达式树,然后把这个对象树编译成真正的代码。在.NET 3.5中的表达式树只能是单一的表达式,不能表示完整的类、方法。这在.NET 4.0中得到了一定的改进,表达式树可以支持动态类型,我们可以创建块,为表达式赋值等等。

将Lambda表达式转换为表达式树

Lambda表达式不仅可以创建委托实例,C# 3.0对于将Lambda表达式转换成表达式树提供了内建的支持。我们可以通过编译器把Lambda表达式转换成一个表达式树,并创建一个Expression<TDelegate>的一个实例。

下面的例子中我们将一个Lambda表达式转换成一个表达式树,并通过代码查看表达式树的各个部分:

C#
static void Main(string[] args)
{
//将Lambda表达式转换为类型Expression<T>的表达式树
//expression不是可执行代码
Expression<Func<int, int, int>> expression = (a, b) => a + b;

Console.WriteLine(expression);
//获取Lambda表达式的主体
BinaryExpression body = (BinaryExpression)expression.Body;
Console.WriteLine(expression.Body);
//获取Lambda表达式的参数
Console.WriteLine(" param1: {0}, param2: {1}", expression.Parameters[0], expression.Parameters[1]);
ParameterExpression left = (ParameterExpression)body.Left;
ParameterExpression right = (ParameterExpression)body.Right;
Console.WriteLine(" left body of expression: {0}{4} NodeType: {1}{4} right body of expression: {2}{4} Type: {3}{4}", left.Name, body.NodeType, right.Name, body.Type, Environment.NewLine);

//将表达式树转换成委托并执行
Func<int, int, int> addDelegate = expression.Compile();
Console.WriteLine(addDelegate(10, 16));
Console.Read();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static void Main(string[] args)
{
    //将Lambda表达式转换为类型Expression<T>的表达式树
    //expression不是可执行代码
    Expression<Func<int, int, int>> expression = (a, b) => a + b;
 
    Console.WriteLine(expression);
    //获取Lambda表达式的主体
    BinaryExpression body = (BinaryExpression)expression.Body;
    Console.WriteLine(expression.Body);
    //获取Lambda表达式的参数
    Console.WriteLine(" param1: {0}, param2: {1}", expression.Parameters[0], expression.Parameters[1]);
    ParameterExpression left = (ParameterExpression)body.Left;
    ParameterExpression right = (ParameterExpression)body.Right;
    Console.WriteLine(" left body of expression: {0}{4} NodeType: {1}{4} right body of expression: {2}{4} Type: {3}{4}", left.Name, body.NodeType, right.Name, body.Type, Environment.NewLine);
 
    //将表达式树转换成委托并执行
    Func<int, int, int> addDelegate = expression.Compile();
    Console.WriteLine(addDelegate(10, 16));
    Console.Read();
}

代码的输出为:

表达式树的用途

前面看到,通过Expression的派生类中的各种节点类型,我们可以构建表达式树;然后可以把表达式树转换成相应的委托类型实例,最后执行委托实例的代码。但是,我们不会绕这么大的弯子来执行委托实例的代码。

表达式树主要在LINQ to SQL中使用,我们需要将LINQ to SQL查询表达式(返回IQueryable类型)转换成表达式树。之所以需要转换是因为LINQ to SQL查询表达式不是在C#代码中执行的,LINQ to SQL查询表达式被转换成SQL,通过网络发送,最后在数据库服务器上执行。

这里只做个简单的介绍,后续会介绍LINQ to SQL相关的内容。

编译器对Lambda表达式的处理

前面我们了解到,Lambda可以用来创建委托实例,也可以用来生成表达式树,这些都是编译器帮我们完成的。

编译器如何决定生成可执行的IL还是一个表达式树:

  • 当Lambda表达式赋予一个委托类型的变量时,编译器生成与匿名方法同样的IL(可执行的委托实例)
  • 当Lambda表达式赋予一个Expression类型的变量时,编译器就将它转换成一个表达式树

下图展示了LINQ to Object和LINQ to SQL中Lambda表达式的不同处理方式:

总结

本文中介绍了Lambda表达式,在匿名方法的基础上进一步简化了委托实例的创建,编写更加简洁、易读的代码。匿名函数不等于匿名方法,匿名函数包含了匿名方法和lambda表达式这两种概念

Lambda不仅可以创建委托实例,还可以由编译器转换成表达式树,使代码可以在程序之外执行(参考LINQ to SQL)。

赞 2 收藏 2 评论

 

C#中的Lambda表达式和表达式树的更多相关文章

  1. 16.C#初见Lambda表达式及表达式树(九章9.1-9.3)

    在说明Lambda相关知识前,我们需要了解Lambda表达式常用于LINQ,那么我们来聊下LINQ. LINQ的基本功能就是创建操作管道,以及这些操作需要的任何状态.这些操作表示了各种关于数据的逻辑: ...

  2. Lambda表达式和表达式树

    在C# 2.0中,通过方法组转换和匿名方法,使委托的实现得到了极大的简化.但是,匿名方法仍然有些臃肿,而且当代码中充满了匿名方法的时候,可读性可能就会受到影响.C# 3.0中出现的Lambda表达式在 ...

  3. C#复习笔记(4)--C#3:革新写代码的方式(Lambda表达式和表达式树)

    Lambda表达式和表达式树 先放一张委托转换的进化图 看一看到lambda简化了委托的使用. lambda可以隐式的转换成委托或者表达式树.转换成委托的话如下面的代码: Func<string ...

  4. 深入学习C#匿名函数、委托、Lambda表达式、表达式树类型——Expression tree types

    匿名函数 匿名函数(Anonymous Function)是表示“内联”方法定义的表达式.匿名函数本身及其内部没有值或者类型,但是可以转换为兼容的委托或者表达式树类型(了解详情).匿名函数转换的计算取 ...

  5. lambda表达式和表达式树(深入理解c#)

    1.Lambda形式 1). Lambda表达式最冗长的形式: (显式类型的参数列表)=>{语句} 2). 大多数时候,都可以用一个表达式来表示主体,该表达式的值是Lambda的结果,在这些情况 ...

  6. C# 表达式树 创建、生成、使用、lambda转成表达式树~表达式树的知识详解

    笔者最近学了表达式树这一部分内容,为了加深理解,写文章巩固知识,如有错误,请评论指出~ 表达式树的概念 表达式树的创建有 Lambda法 和 组装法. 学习表达式树需要 委托.Lambda.Func& ...

  7. 【C++】C++中的lambda表达式和函数对象

    目录结构: contents structure [-] lambda表达式 lambda c++14新特性 lambda捕捉表达式 泛型lambda表达式 函数对象 函数适配器 绑定器(binder ...

  8. 无法将具有语句体的lambda表达式转换为表达式树

    很早就碰到了这个问题,当时也没有深入的研究,趁着空闲,遂把这个问题研究清楚. (一)普通案例 下面从一个普通的案例入手,下面准备两个List集合,都是放在内存里面的(需要模拟到远端执行的时候,我们是通 ...

  9. [转]Pythoin中的Lambda表达式

    引用自:http://www.cnblogs.com/evening/archive/2012/03/29/2423554.html 在学习python的过程中,lambda的语法时常会使人感到困惑, ...

随机推荐

  1. MVC 4.0 学习中遇到的bug

     1.0 _ViewStart.cshtml  _ViewStart.cshtml 里面的如果写了 <script src="/Scripts/jquery-1.8.2.js" ...

  2. js清除cookie

    例如要清除键为abc的cookie: document.cookie = "abc=0;expires=" + new Date().toUTCString();

  3. 用node-webkit(NW.js)创建桌面程序

    以往写windows桌面程序需要用MFC.C#之类的技术,那么如果你只会web开发技术呢?或者说你有一个网站,但是你想把你的网站打包成一个桌面应用程序,该如何做呢? 答案就是用node-webkit这 ...

  4. MySQL Information Functions

    Table 12.18 Information Functions Name Description BENCHMARK() Repeatedly execute an expression CHAR ...

  5. php中opendir函数用法实例

    这篇文章主要介绍了php中opendir函数用法,以实例形式详细讲述了opendir函数打开目录的用法及相关的注意事项,具有一定的参考借鉴价值,需要的朋友可以参考下 本文实例分析了php中opendi ...

  6. Oracle常用

    Oracle恢复误删的数据或表,解除锁定SQL或table   转载于: http://renjie120.iteye.com/ 注释:本文转自网络,转载请注明注意:数据库版本是10g,不过大部分9i ...

  7. Android提升篇系列:Activity recreate(Activity 重新创建/自我恢复)机制(一)

    注:本文中的recreate是指当内存不足时,Activity被回收,但再次来到此Activity时,系统重新恢复的过程.例如:当Activity A到Activity B时,如果内存不足,A被回收, ...

  8. ssh默认端口更改后,如何正常使用git?

    由于安全或者其它原因,我们可能会修改默认的SSH服务端口号,默认情况下,已有的git项目在pull或者push的时候会报错. 现在假设原来的项目的remote设置为git@domain.com:Pro ...

  9. 使用React并做一个简单的to-do-list

    1. 前言 说到React,我从一年之前就开始试着了解并且看了相关的入门教程,而且还买过一本<React:引领未来的用户界面开发框架 >拜读.React的轻量组件化的思想及其virtual ...

  10. 20款响应式的 HTML5 网页模板【免费下载】

    下面的列表集合了20款响应式的 HTML5 网页模板,这些专业的模板能够让你的网站吸引很多的访客.除了好看的外观,HTML5 模板吸引大家的另一个原因是由于其响应性和流动性.赶紧来看看. 您可能感兴趣 ...