在C#2中,由于有了方法组,匿名方法,类型的协变和抗变,使得运用delegate变得很容易,在注册事件时代码变得简单易读,但是在C# 2中,代码仍然有点臃肿,大块的匿名方法会降低代码的可读性,一般我们不会在一条语句中写多个匿名方法。
LINQ产生的一个目的是能够方便的对数据进行管道操作而不失语义。LINQ能够表达对数据进行的各种逻辑操作,LINQ执行时,这些操作实际上都是通过委托来实现的。使用LINQ to Object操作数据时,一条语句中包含多个委托是很常见的,C# 3中的Lambda表达式正式这幕后的功臣,它不仅使得我们能够在一条代码中写多个委托,而且不会丧失代码的可读性。相信用过LINQ的人应该都有这样的体会。
在很多方面Lambda表达式可以看成是C#2中匿名方法的演变。他们俩的功能是一样的,都使得代码更加清晰和紧凑。另外,Lambda表达式和匿名方法在闭包的特性上是一致的,但是Lambda表达式还有一些小的特性,能够使得代码在大多数情况下更加紧凑。和匿名方法一样,Lambda表达式有着自己的转换规则---表达式的类型并不是一个委托的类型,但是它可以显示或者隐式的转换为一个委托的实例。
下面来看看Lambda表达式是如何来表示委托的。我们先从一个简单的例子开始。首先我们写一个以String类型为参数,返回Int32类型的值的委托实例。然后演示委托如何一步一步转换为Lambda表达式。
一、Func<……>泛型委托类型
首先我们要选委托类型,在.NET 3.5中,提供了一系列泛型委托类型。在.NET3.5中有5个名为Func的泛型委托类型。每一个类型里面包含0至4个类型参数。5个Func泛型类型签名如下:
TResult Func() TResult Func (T arg) TResult Func (T1 arg1, T2 arg2) TResult Func (T1 arg1, T2 arg2, T3 arg3) TResult Func (T1 arg1, T2 arg2, T3 arg3, T4 arg4)
尖括号中的,都是类型参数,例如,Func
public delegate Int32 SomeDelegate(String arg1, Double arg2)
.NET 3.5中还有一个名为Action<……>的泛型委托类,他的用法和Func的差不多,只是他的返回值为void。如果您觉得5个参数不够用的话,在.NET 4.0中,Action<……>和Func<……>的参数扩展到了16个,如Func
在我们这个例子中,我们需要一个类型,他接受String作为参数,返回Int32类型的值,所以我们可以使用Func
二、转化为Lambda表达式的第一步
根据上面的委托类型,我们可以使用匿名方法创建一个委托实例。如下,该实例返回String参数的长度。
Func<String, Int32> returnLength; returnLength = delegate(String text) { return text.Length; }; Console.WriteLine(returnLength("Hello"));
上面的例子中,将输出5. 上面的以delegate开头的是匿名表达式。现在我们开始对这部分进行转换。最常见的Lambda表达式的形式如下:
(参数类型1 参数名1 [ , 参数类型2 参数名2 ……]) => { 方法内部表达式 }
=>是在C# 3中引入的,他告诉编译器,我们将使用Lambda表达式,大多数的使用Lambda表达式代表委托的方法都有一个返回值。但在C# 1中,委托通常用于事件,所以很少有返回值。在LINQ中,委托用做数据管道的一部分,使得数据能够进行各种映射,过滤等操作。
下面我们使用Lambda表达式来表示该委托的实例,代码如下:
Func<String, Int32> returnLength; returnLength = (String text)=> { return text.Length; }; Console.WriteLine(returnLength("Hello"));
上面的例子返回的结果和之前的一样,在大括号中,我们可以写任意表达式,只要返回值为String类型。
三、使用单个表达式作为函数体
前面的例子中,我们使用一对大括号将返回值表达式括起来了。这样做很灵活,你可以在括号内写多条语句,进行各种操作,就像在匿名方法中的那样。但是大多数情况下,通常可以将这个函数体表示为单个表达式,这个表达式的值就是Lambda表达式的值。在这种情况下,我们可以省去左右的大括号和逗号,表达式样子下面的样子。
(参数类型1 参数名1 [ , 参数类型2 参数名2 ……]) =>方法表达式
这样,上面例子中的右边部分可以改写为:
returnLength = (String text) => text.Length;
现在看起来简洁多了。那参数类型怎么办呢,由于编译器已经知道他是Func
四、隐式类型参数列表
在大多数情况下,编译器能够推断出参数的类型,而不需要我们现实的去声明,因此,Lambda表达是可以改写为:
(参数名1 [ ,参数名2 ……]) =>方法表达式
隐式类型参数列表,就是一系列用逗号分隔的参数名称。这些参数类型,要么全部都显示声明类型,要么全部不声明类型让编译器推断,不能一部分显示声明,一部分隐式声明。如果有参数类型为out或者in的话,就必须显示声明参数类型。这样我们的Lambda表达式可以改写为:
returnLength = (text) => text.Length;
现在看起来简洁多了。最后唯一一点有点不爽的是,参数有个括号。
五、去掉单参数两侧的括号
当Lambda表达式只有一个参数的额时候,C# 3运行我们省略掉两边的括号,整个Lambda表达式变为下面的形式:
参数名 =>方法表达式
根据这一规则,之前例子中的Lambda表达个可以改为:
returnLength = text => text.Length;
你可能会想,哪有这么多特例啊,参数恰好只有一个,函数主题恰好能用一个表达式表达完。这里是为了展示Lambda表达式如何简化代码提高可读性,很多时候,当大量的这样的情况出现时,使用Lambda表达式能够极大提高代码可读性。现在整个方法可以写成下面这样:
Func<String, Int32> returnLength; returnLength = text => text.Length; Console.WriteLine(returnLength("Hello"));
现在看起来简洁多了,可能第一次觉得这样写怪怪的,就像第一次用匿名方法和LINQ那样,用久了就习惯了。当使用Lambda表达式时,你能够体会到在创建一个委托实例时时多磨的简洁。上面例子中,我们可以把变量text换乘其他名字比如x,在LINQ中经常这样,但是长一点的变量可以更加容易阅读。下图总结了以上的步骤:
六、总结
本文以一个特例演示了匿名方法如何一步一步转换为Lambda表达式。从转化中您可以体会到Lambda表达式的简洁,希望这些对您了解Lambda表达式有点帮助。