在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泛型类型签名如下:
[C#] 纯文本查看 复制代码 TResult Func<TResult>()
TResult Func<T,TResult>(T arg)
TResult Func<T1,T2,TResult>(T1 arg1, T2 arg2)
TResult Func<T1,T2,T3,TResult>(T1 arg1, T2 arg2, T3 arg3)
TResult Func<T1,T2,T3,T4,TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4)
尖括号中的,都是类型参数,例如,Func<String,Double,Int32> 表示该委托接受两个参数,第一个是String,第二个是Double,返回Int32类型,所有适合该类型的方法都可以使用该委托。Func<String,Double,Int32>等价如下面的委托:
[code=csharp]public delegate Int32 SomeDelegate(String arg1, Double arg2)[/code]
.NET 3.5中还有一个名为Action<……>的泛型委托类,他的用法和Func的差不多,只是他的返回值为void。如果您觉得5个参数不够用的话,在.NET 4.0中,Action<……>和Func<……>的参数扩展到了16个,如Func<T1,……,T16,TResult>,这么多的参数主要是为了支持DLR。
在我们这个例子中,我们需要一个类型,他接受String作为参数,返回Int32类型的值,所以我们可以使用Func<String,Int32>。
二、转化为Lambda表达式的第一步 根据上面的委托类型,我们可以使用匿名方法创建一个委托实例。如下,该实例返回String参数的长度。
[C#] 纯文本查看 复制代码 Func<String, Int32> returnLength;
returnLength = delegate(String text) { return text.Length; };
Console.WriteLine(returnLength("Hello"));
上面的例子中,将输出5. 上面的以delegate开头的是匿名表达式。现在我们开始对这部分进行转换。最常见的Lambda表达式的形式如下:
[code=csharp](参数类型1 参数名1 [ , 参数类型2 参数名2 ……]) => { 方法内部表达式 }[/code]
=>是在C# 3中引入的,他告诉编译器,我们将使用Lambda表达式,大多数的使用Lambda表达式代表委托的方法都有一个返回值。但在C# 1中,委托通常用于事件,所以很少有返回值。在LINQ中,委托用做数据管道的一部分,使得数据能够进行各种映射,过滤等操作。
下面我们使用Lambda表达式来表示该委托的实例,代码如下:
[C#] 纯文本查看 复制代码 Func<String, Int32> returnLength;
returnLength = (String text)=> { return text.Length; };
Console.WriteLine(returnLength("Hello"));
上面的例子返回的结果和之前的一样,在大括号中,我们可以写任意表达式,只要返回值为String类型。
三、使用单个表达式作为函数体 前面的例子中,我们使用一对大括号将返回值表达式括起来了。这样做很灵活,你可以在括号内写多条语句,进行各种操作,就像在匿名方法中的那样。但是大多数情况下,通常可以将这个函数体表示为单个表达式,这个表达式的值就是Lambda表达式的值。在这种情况下,我们可以省去左右的大括号和逗号,表达式样子下面的样子。
[C#] 纯文本查看 复制代码 (参数类型1 参数名1 [ , 参数类型2 参数名2 ……]) =>方法表达式
这样,上面例子中的右边部分可以改写为:
[C#] 纯文本查看 复制代码 returnLength = (String text) => text.Length;
现在看起来简洁多了。那参数类型怎么办呢,由于编译器已经知道他是Func<String,Int32>的一个实例,该实例接受一个String类型的参数,所以我们可以直接将参数卸载括号内,从而省略参数类型。
四、隐式类型参数列表 在大多数情况下,编译器能够推断出参数的类型,而不需要我们现实的去声明,因此,Lambda表达是可以改写为:
[C#] 纯文本查看 复制代码 (参数名1 [ ,参数名2 ……]) =>方法表达式
隐式类型参数列表,就是一系列用逗号分隔的参数名称。这些参数类型,要么全部都显示声明类型,要么全部不声明类型让编译器推断,不能一部分显示声明,一部分隐式声明。如果有参数类型为out或者in的话,就必须显示声明参数类型。这样我们的Lambda表达式可以改写为:
[C#] 纯文本查看 复制代码 returnLength = (text) => text.Length;
现在看起来简洁多了。最后唯一一点有点不爽的是,参数有个括号。
五、去掉单参数两侧的括号 当Lambda表达式只有一个参数的额时候,C# 3运行我们省略掉两边的括号,整个Lambda表达式变为下面的形式:
[C#] 纯文本查看 复制代码 (参数名) =>方法表达式
根据这一规则,之前例子中的Lambda表达个可以改为:
[C#] 纯文本查看 复制代码 returnLength = text => text.Length; 你可能会想,哪有这么多特例啊,参数恰好只有一个,函数主题恰好能用一个表达式表达完。这里是为了展示Lambda表达式如何简化代码提高可读性,很多时候,当大量的这样的情况出现时,使用Lambda表达式能够极大提高代码可读性。现在整个方法可以写成下面这样:
[C#] 纯文本查看 复制代码 Func<String, Int32> returnLength; returnLength = text => text.Length; Console.WriteLine(returnLength("Hello")); 现在看起来简洁多了,可能第一次觉得这样写怪怪的,就像第一次用匿名方法和LINQ那样,用久了就习惯了。当使用Lambda表达式时,你能够体会到在创建一个委托实例时时多磨的简洁。上面例子中,我们可以把变量text换乘其他名字比如x,在LINQ中经常这样,但是长一点的变量可以更加容易阅读。下图总结了以上的步骤:
|