使用lambda表达式的一个场合是把lambda表达式赋予委托类型:在线实现代码。只要有委托参数类型的地方,就可以利用lambda表达式。前面使用匿名方法的例子可以改为使用lambda表达式。
using System;
namespace lambda
{
class Program
{
static void Main(string[] args)
{
string mid = ", middle part";
Func<string,string> lambda = param =>{
param += mid;
param += " and this was added to the string";
return param;
};
string result = "Hello";
Console.WriteLine("Start of string");
System.Console.WriteLine($"{lambda(result)}");
}
}
}
lambda运算符“=>”的左边列出了需要的参数,而其右边定义了赋予labmda变量的方法的实现代码。
参数
lambda表达式有几种定义参数的形式。如果只有一个参数,只写出参数名就足够了。下面的lambda表达式使用了参数s。因为委托类型定义了一个string参数。实现代码调用方法返回一个字符串,在调用该委托时,就把字符串最终写入控制台。
Func<string,string> oneParam = s =>$"change uppercase {s.ToUpper()}";
System.Console.WriteLine(oneParam("test"));
如果委托使用多个参数就把这些参数名放在圆括号中。这里参数x和y的类型是double,由Func<double,double>委托定义:
Func<double,double,double> twoParam = (x,y) => x*y;
System.Console.WriteLine(twoParam(3,2));
为了方便起见,可以在圆括号中给变量名添加参数类型。如果编译器不能匹配重载后的版本,那么使用参数类型可以帮助找到匹配的委托。
Func<double,double,double> twoParamWithTypes = (double x,double y)=>x*y;
System.Console.WriteLine(twoParamWithTypes(4,2));
多行代码
如果lambda表达式只有一条语句,在方法块内就不需花括号和return语句,因为编译器会添加一条隐式的return语句:
Func<double,double> squre = x =>x*x;
添加花括号、return语句和分号是完全合法的,通常这比不添加这些符号更容易阅读:
Func<double,double> squre = x =>
{
return x*x;
};
但是,如果在lambda表达式的实现代码中需要多条语句,就必须添加花括号和return语句:
string mid = ", middle part";
Func<string,string> lambda = param =>{
param += mid;
param += " and this was added to the string";
return param;
};
闭包
通过lambda表达式可以访问lambda表达式块外部的变量,这称为闭包。闭包是非常好用的功能,但如果使用不当也会非常的危险。
在下面的示例中,Func<int,int>类型的lambda表达式需要一个int参数,返回一个int值。该lambda表达式的参数用变量x定义。实现代码还访问了lambda表达式外部的someVal。只要不假设在调用f时,lambda表达式创建了一个以后使用的新方法,这似乎没有什么问题。看看下面的代码块,调用f的返回值应是x加5的结果,但实情似乎不是这样:
int someVal = 5;
Func<int,int> f = x=>x+someVal;
假定以后要修改变量someVal,于是调用lambda表达式时,会使用someVal的新值。调用f(3)的结果是10:
someVal = 7;
System.Console.WriteLine(f(3));
同样,在lambda表达式中修改闭包的值,可以在lambda表达式的外部访问已改动的值。
现在我们也许会奇怪,如何在lambda表达式的内部访问lambda表达式外部的变量。为了理解这一点,看看编译器在定义lambda表达式时做了什么。对于lambda表达式x=>x+someVal,编译器会创建一个匿名类,它有一个构造函数来传递外部变量。该构造函数取决于从外部访问变量数。对于这个简单的例子,构造函数接受一个int值。匿名类包含一个匿名方法,其实现代码、参数和返回类型由lambda表达式定义:
public class AnonymousClass{
private int someVal;
public AnonymousClass(int someVal){
this.someVal = someVal;
}
public int AnonymousMethod(int x) => x+someVal;
}
使用lambda表达式并调用该方法,会创建匿名类的一个实例,并传递调用该方法时变量的值。
注:
如果给多个线程使用闭包,就可能遇到并发冲突。最好仅给闭包使用不变的类型。这样可以确保不改变值,也不需要同步
lambda表达式可以用于类型为委托的任意地方。类型是Expression或Expression<t>时,也可以使用lambda表达式,此时编译器会创建一个表达式树。