Lambda表达式可以采用以下任意一种形式的表达式:
1.表达式Lambda,表达式为其主体:
(input-parameters) => expression
2.语句Lambda,语句块作为其主体:
(input-parameters) => {<sequence-of-statements>}
使用Lambda声明运算符=>,从其主体中分离Lambda参数列表。若要创建Lambda表达式,需要在Lambda运算符左侧指定输入参数(如果有参数时),然后在另一侧输入表达式或语句块。
任何Lambda表达式都可以转换为委托类型,可以转换的委托类型由参数和返回值的类型定义。如果Lambda表达式不返回值,则可以将其转换为Action
委托类型之一;否则,可将其转换为Func
委托类型之一。例如,有两个参数且不返回值的Lambda表达式可转化为Action
委托。有一个参数且不返回值的Lambda表达式可转换为Func
委托。
例如:Lambda表达式x => x * x
,指定名为x的参数,并返回x的平方值,并将表达式x => x * x
分配给委托类型的变量。
Func<int, int> Square = x => x * x;
Console.WriteLine($"输出:{Square(10)}");
//输出:100
表达式Lambda还可以转换为表达式树类型,如下示例所示:
System.Linq.Expressions.Expression<Func<int, int>> e = x => x * x;
Console.WriteLine($"输出:{e}");
//输出:x => (x * x)
可在需要委托类型或表达式树的实列的任何代码中使用Lambda表达式。用C#编写LINQ时,还可以使用Lambda表达式,如下所示:
int[] numbers = { 1, 5, 3, 9, 28, 35, 5, 32, 34 };
var squaredNumbers = numbers.Select(x => x * x);
Console.WriteLine(string.Join("", squaredNumbers));
表达式Lambda
表达式位于=>运算符右侧的Lambda表达式称为“表达式Lambda”。表达式Lambda广泛用于表达式树的构造,表达式Lambda会返回表达式的结果,并采用以下基本形式:
(input-parameters) => experssion
仅当Lambda只有一个输入参数时,括号才是可选择的,否则括号是必须的。
使用空括号指定零个输入参数:
Action action = () => Console.WriteLine();
括号内的两个或更多的输入参数使用逗号加以分离:
Func<int, int, bool> test = (x, y) => x == y;
有时编译器无法推断输入类型,可以显示指定类型:
Func<int, string, bool> testTo = (int x, string str) => str.Length > x;
输入参数必须全部为显示或者全部为隐式;否则,编译器会产生报错。
表达式Lambda的主体可以包含方法调用。不过,若要创建在.NET公共语言运行时的上下文之外(如SQL Server中)计算的表达式树,不能在Lambda表达式中使用方法调用。因为在.NET公共语言运行时的上下文之外,方法将没有任何意义。
语句Lambda
语句Lambda与表达式Lambda表达类似,只是语句括在大括号中:
(input-parameters) => {}
语句Lambda的主体可以包含任意数量的语句;但是,通常不会多余两个或三个。
Action action = name =>
{
string greeting = $“你好,{name}”;
Console.WriteLine(greeting);
};
action(“莫扎特”);
语句Lambda也不能用于创建表达式目录树。
异步Lambda
通过使用async和await关键字,可以轻松创建包含异步处理的Lambda表达式和语句。
例如,通过Windows窗体示例包含一个调用和等待异步方法ExampleMethodAsync
的事件处理程序。
public partial class Form1 : Form {
public Form1()
{
InitializeComponent();
button1.Click += button1_Click;
}
private async void button1_Click(object sender, EventArgs e)
{
await ExampleMethodAsync();
textBox1.Text += "\r\nControl returned to Click event handler.\n"; }
private async Task ExampleMethodAsync()
{
// The following line simulates a task-returning asynchronous
process. await Task.Delay(1000);
}
}
可以使用异步Lambda添加同一事件处理程序。若要添加此程序,请在Lambda参数列表前添加async修饰符,如下面示例所示:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
button1.Click += async (sender, e) =>
{
await ExampleMethodAsync();
textBox1.Text += "\r\nControl returned to Click event handler.\n";
};
}
private async Task ExampleMethodAsync()
{
// The following line simulates a task-returning asynchronous process.
await Task.Delay(1000);
}
}
Lambda表达式和元组
从C#7.0起,C#语言提供对元组的内置支持。可以提供一个元组作为Lambda表达式的参数,同时Lambda表达式也可以返回一个元组。在某些情况下,C#编译器使用类型推理来确定元组组件的类型。
可通过用括号括住用逗号分割的组件列表来定义元组。下面的示例使用包含三个组件的元组,将一系列数字传递给Lambda表达式,此表达式将每个值翻倍,然后返回包含乘法运算结果的元组(内含三个元组)。
Func<(int, int, int), (int, int, int)> Items = s => (2 * s.Item1, 2 * s.Item2, 2 * s.Item3);
var nums = (3, 7, 4);
var numbersItem = Items(nums);
Console.WriteLine($"The set {nums} doubled:{numbersItem}");
通常,元组字段命名为Item1、Item2等。但是,可以使用命名组件定义元组,如以下示例所示:
Func<(int n1, int n2, int n3), (int, int, int)> items = s => (2 * s.n1, 2 * s.n2, 2 * s.n3);
var num = (45, 34, 29);
var itemNums = items(num);
Console.WriteLine($"The set {num} doubled:{itemNums}");
Lambda表达式中的类型推理
编写Lambda时,通常不必为输入参数指定类型,因为编译器可以根据Lambda主体、参数类型以及C#语言规范中描述的其它因素来推断类型。对于大多数标准查询运算符,第一个输入是源序列中的元素类型。如要查询IEnumerable
,则输入变量将被推断为Customer对象,这就意味着你可以访问其属性和方法。
customer.Where(c => c.City == "London");
Lambda类型推理的一般规则如下:
注意: Lambda表达式本身没有类型,因为通用类型系统没有Lambda表达式这一固有概念。不过,有时以一种非正式的方式谈论Lambda表达式的“类型”会很方便,在这些情况下,类型是指委托类型或Lambda表达式所转换到的Expression类型。
捕获Lambda表达式中的外部变量和变量范围
Lambda可以引用外部变量。这些变量是在定义Lambda表达式的方法中或包含Lambda表达式的类型中的范围内变量。以这种方式捕获的变量将进行存储以备在Lambda表达式中使用,即使在其它情况下,这些变量将超出范围并进行垃圾回收。必须明确地分配外部变量,然后才能在Lambda表达式中使用该变量。
如下示例所示:
public class Class1
{
public class VariableCaptureGame
{
internal Action<int> updateCapturedLocalVariable;
internal Func<int, bool> isEqual;
public void RunCaptureGame(int data)
{
int k = 0;
updateCapturedLocalVariable = x =>
{
k = x;
bool result = k > data;
Console.WriteLine($"{k} is greater than {data}:{result}");
};
isEqual = x => x == k;
Console.WriteLine($"Local variable before lambda invocation:{k}");
updateCapturedLocalVariable(20);
Console.WriteLine($"Local variable after lambda invocation:{k}");
}
}
public void InvokeMain()
{
var game = new VariableCaptureGame();
int gameInput = 8;
game.RunCaptureGame(gameInput);
int jTry = 10;
bool result = game.isEqual(jTry);
Console.WriteLine($"Captured local variable is equal to {jTry}:{result}");
int anotherData = 6;
game.updateCapturedLocalVariable(anotherData);
bool anotherResult = game.isEqual(anotherData);
Console.WriteLine($"Another lambda observes a new value of captured variable:{anotherResult}");
}
}
下列规则适用于Lambda表达式中的变量范围:
捕获的变量将不会作为垃圾回收,直至引用变量的委托符合垃圾回收的条件;
在封闭方法中看不到Lambda表达式内引入的变量;
Lambda表达式无法从封闭方法中直接捕获in、ref
或out
参数;
Lambda表达式中的return语句不会导致封闭方法返回;
如果相应跳转语句的目标位于Lambda表达式块之外,Lambda表达式不得包含goto、break或continue语句。同样,如果目标在块内部,在Lambda表达式块外部使用跳转语句也是错误的。