作为委托的Lambda表达式
从很多方面看, Lambda表达式可以被看成是C# 2中匿名方法的进化. 几乎没有什么匿名方法能做而Lambda表达式不能做的事情, 而Lambda表达式几乎总是能提供更好的可读性且更简洁. 特别的, lambda表达式和匿名方法捕获变量的行为是完全一致的. 在两者多数的显式行为当中并没有存在太多的不同——不过lambda表达式拥有大量的快捷方式, 这在很多常规情况下使得代码更加简洁. 与匿名方法一样, lambda表达式也拥有自己特殊的转换规则——表达式本身的类似并不是一个委托类型, 但其可以通过多种方法被隐式或显式转换成为一个委托实例. 术语匿名函数(anonymous function)覆盖了匿名方法和lambda表达式——因为在很多案例中两者都可以应用相同的转换规则.
接下来我们将从一个简单的例子开始, 首先我们将使用匿名方法. 我们将会创建一个委托实例, 其带有一个string类型的参数并且返回int作为结果(计算string的长度). 首先我们选择使用委托类型. 幸运的是.NET 3.5拥有一系列的泛型委托类型可以帮助我们.
Func<…>委托类型
.NET 3.5的System命名空间下有5个泛型Func委托类型. Func并没有什么特别之处——仅仅是让你方便的拥有了一些预定义的泛型委托, 它们可以适用于多种情况. 每一个委托签名分别拥有0到4个参数, 其类型就是参数本身被赋予的类型, 最后一个参数则是返回值的类型. 以下是所有Func委托类型的签名:
1: public delegate TResult Func<TResult>()
2: public delegate TResult Func<T,TResult>(T arg)
3: public delegate TResult Func<T1,T2,TResult>(T1 arg1, T2 arg2)
4: public delegate TResult Func<T1,T2,T3,TResult>
5: (T1 arg1, T2 arg2, T3 arg3)
6: public delegate TResult Func<T1,T2,T3,T4,TResult>
7: (T1 arg1, T2 arg2, T3 arg3, T4 arg4)
例如, Func<string, double, int>等价于一个来自于下面代码的委托类型:
1: delegate int SomeDelegate(string arg1, double arg2)
Action<…>提供了等价的功能, 唯一的区别就是其没有任何的返回值. 单一单数的Action存在于.NET 2.0中, 其他的全部都是.NET 3.5中新引入的。因为我们的例子将会拥有一个string参数, 并且返回int类型, 因此我们将使用Func<string,int>.
首次转换到lambda表达式
现在我们知道了委托类型, 我们可以首先使用匿名方法创建委托实例, 如下所示:
1: Func<string,int> returnLength;
2: returnLength = delegate (string text) { return text.Length; };
3: Console.WriteLine (returnLength("Hello"));
执行结果控制台打印出”5″, 与我们期望的一样, 接下来让我们尝试将它转换为lambda表达式. 最冗长的lambda表达式格式为:
(explicitly-typed-parameter-list) => { statements }
=>是C# 3中新引入的, 其告诉编译器我们正在使用lambda表达式. 多数时候, lambda表达式会和一个带有nonvoid返回类型的委托一起使用. 在C# 1中, 委托通常是被事件使用而很少返回值, 虽然lambda表达式也可以同样适用, 但更多时候当它们需要返回值时用lambda表达式看起来将更加高雅.
下面的代码与前面的例子结果一致, 只不过是用的lambda表达式:
1: Func<string,int> returnLength;
2: returnLength = (string text) => { return text.Length; };
3: Console.WriteLine (returnLength("Hello"));
当读到lambda表达式的时候, 我们可以将=>想像成为”goes to”, 因此上述代码可以被理解成text goes to text.length. 在匿名方法中给定的规则同样适用于lambda表达式: 你不能从一个lambda表达式中尝试返回一个void的返回类型. 目前为止我们并没有节省太多的空间而且让代码及其容易的阅读, 现在让我们开始使用一些快捷方式.
使用单一表达式作为主体
目前为止我们使用了一整块的代码去返回值, 这是非常灵活的, 因为我们可以使用多行语句, 执行循环, 在代码块内部的多个地方返回值等等, 与匿名方法一样. 然而很多时候我们只需要一个单一的表达式就可以, 它的执行值就是整个lambda表达式的值. 在这些案例中, 我们可以仅仅指定表达式本身, 没有任何的大括号, 返回语句和分号. 其格式为:(explicitly-typed-parameter-list) => expression
在我们的例子中, 这意味着我们的lambda表达式变为:
1: (string text) => text.Length
现在, 这看起来已经开始变得简单, 那么, 参数类型呢? 编译器已经知道Func<string, int>需要的是一个string的参数, 因此我们能够仅仅命名该参数…
隐式类型的参数列表
多数时候, 即使你没有显式指明, 编译器依然可以猜测参数类型. 在这些案例中, 我们可以将lambda表达式写成这样:
(implicitly-typed-parameter-list) => expression
一个隐式类型的参数列表就是一个用逗号分开的名称列表, 你不能将两种不同方式组成的方式混合在一起——要吗他们都是隐式的, 要吗他们都是显式的. 另外, 如果有任何的参数是out或者ref类型的参数, 你只能使用显式类型参数. 在我们的例子中没有什么问题, 因此我们的lambda表达式变为:
1: (text) => text.Length
现在代码已经变得非常简短——已经没有什么太多东西我们可以进一步剔除的了. 虽然参数看起来有一点冗长.
单一参数的快捷方式
当lambda表达式仅仅需要一个参数, 并且该参数可以是隐式类型, C# 3允许我们省略圆括号, 如下:
parameter-name => expression
因此我们的表达式最后将变成:
1: text => text.Length
你可以会好奇为什么lambda表达式会有这么多特殊的规则——例如, 语言的其他部分并不会去关心方法拥有一个参数或多个参数. 然后, 这看起来似乎很特别的例子实际上是及其常见的, 而且从参数列表当中移除圆括号对于提高可读性是很有意义的, 特别是在一小段代码当中拥有多个lambda表达式的时候. 值得一提的是你还是可以像其他表达式那样使用圆括号, 如果你确实想这么做的话. 有时候, 当你想把lambda表达式赋值给一个变量或者属性的时候, 这对于提高可读性是有帮助的——否则, 等号可能会让人有点混淆, 下面的代码使用了这种做法:
1: Func<string,int> returnLength;
2: returnLength = (text => text.Length);
3: Console.WriteLine (returnLength("Hello"));
一开始你可能会觉得上面的代码有一点点难以阅读, 这与匿名方法第一次出现的时候是一样的, 直到开发人员开始习惯使用它们. 当你开始习惯使用lambda表达式的时候, 你一定会感激它们是多么的简洁方便. 难以想象还有更短更清晰的方法可以用于创建委托实例. 我们当然可以将变量名从text改为x, 实际上, 在LINQ当中, 这是很常用的, 然而, 更长的名字给阅读人员带来了更多的信息.