C# 讲座 - 第 9 讲: Lambda 表达式






4.91/5 (17投票s)
这是我的系列讲座的第 9 讲。这一讲相对较短,但它是 LINQ 部分的介绍。
全部课程集
- C#Lectures - 第 1 讲:
原始类型 - C# 课程 - 第2课:在 C# 中处理文本:char, string, StringBuilder, SecureString
- C# 课程 - 第3课:C# 类型设计。你必须知道的类基础知识
- C# 课程 - 第4课:面向对象编程基础:C# 示例中的抽象、封装、继承、多态
- C# 课程 - 第5课:C# 示例中的事件、委托、委托链
- C# 课程 - 第6课:C# 中的特性和自定义特性
- C# 讲座 - 第 7 讲:
反射( 通过 C# 示例) - C# 课程 - 第8课:灾难恢复。C# 示例中的异常和错误处理
- C# 讲座 - 第 9 讲:
Lambda 表达式 - C# 课程 - 第10课:LINQ 简介,LINQ to Objects 第一部分
引言
在本文中,我将讨论被称为 Lambda 表达式的匿名函数。Lambda 表达式是一项相对较新的技术,允许你创建委托或表达式树类型。使用 Lambda 表达式,你可以创建作为参数传递给其他函数或作为函数调用返回值传递的函数。此外,Lambda 表达式在 LINQ 中非常有用。大多数开发人员和作家将 Lambda 表达式与委托和事件一起描述,但由于这是一项相对较新的技术,我决定为它专门写一篇文章。从版本 3 开始,C# 就支持 Lambda 表达式。Lambda 表达式自 LISP 语言开始就已在计算机语言中使用;它们最早由美国数学家 Alonzo Church 于 1936 年构思。这些表达式提供了用于指定算法的简写语法。
匿名类型
在深入研究 Lambda 表达式之前,我想快速强调另一个简短的主题——匿名类型。匿名类型提供了一种便捷的方式,可以将只读属性封装到单个对象中,而无需为此对象定义类型。在编译时,编译器会为匿名类型生成一个名称,但该名称对开发人员不可见。匿名类型中只读属性的类型也由编译器定义。匿名类型本身似乎没有什么了不起的,但与 LINQ 和 Lambda 表达式结合使用时,它们会提供强大的功能。我们将在本章稍后对此进行回顾。
以下代码演示了匿名类型的用法。
//---------------ANONYMOUS TYPES------------------------ Console.WriteLine("-----------ANONYMOUS TYPES----------------"); var anonym1 = new { int_val = 1, double_val = 0.11, string_val = "some string" }; Console.WriteLine("anonym1 type is: " + anonym1.GetType()); Console.WriteLine("double_val type is: " + anonym1.double_val.GetType()); Console.WriteLine("int_val type is: " + anonym1.int_val.GetType()); Console.WriteLine("string_val type is: " + anonym1.string_val.GetType()); Result of it will be:
匿名类型限制
- 它们只能有公共只读属性。
- 它们没有方法或其他类型成员。
- 你只能将匿名类型强制转换为 object。
- 如果两个匿名类型具有相同的定义,则编译器会将它们视为相同的类型并共享相同的元数据。
初始化匿名类型有几种方法。下面提供了一些。
int i = 10; string s = "string value"; var anonym2 = new { int_val = i, string_val = s }; var anonym3 = new { i, s }; var anonym4 = new { anonym2, anonym3 };
构建 Lambda 表达式,Lambda 运算符
创建 Lambda 表达式时,你应该使用 **Lambda 运算符** **=>**。Lambda 运算符将左侧的输入变量与右侧的 Lambda 主体分隔开。Lambda 运算符可以读作“转到”。在 Lambda 表达式中,你将输入参数(如果有)放在运算符的左侧,将表达式或语句放在 Lambda 运算符的右侧。因此,Lambda 表达式看起来像:
<参数(如果有)> => <表达式>
或
<参数(如果有)> => <语句块>
最初,在 Lambda 表达式之前,设计了匿名方法,你可以将其传递给期望委托的函数。有时这非常方便,在代码中可读性也很高。Lambda 随后被创建,为委托提供了更具体的匿名方法语法,当然,它们在 LINQ 查询表达式中也非常有用,因为它们提供了编写可以作为参数传递以供后续评估的紧凑方法的能力。
下面是如何用 Lambda 函数替换委托的示例。
//Declarign the delegate: public delegate int MyDelegate(string s, int i); //Implement the caller and delegate: public static void DelegateCaller(MyDelegate input) { int res = input("Delegate Caller", 5); Console.WriteLine("Delegate result is: " + res); } public static int DelegateFunction(string s, int i) { Console.WriteLine("Delegate function string: " + s + " int:" + i); return 1; } //calling via delegate and via lambda Console.WriteLine("-----------LAMBDA EXPRESSIONS----------------"); //calling by delegate DelegateCaller(new MyDelegate(DelegateFunction)); //creating lambda delegate MyDelegate del = (str, dig) => { Console.WriteLine("Delegate lambda string: " + str + " int: " + dig); return 2; }; DelegateCaller(new MyDelegate(del));
要尝试此示例,你可以下载本文附带的源代码。
Lambda 表达式语法
- 如果需要传递多个参数,可以在括号中指定参数。
(car, engineSize) => car.engine >= engine Size
- 你可以为参数指定类型,以避免混淆。
(ClassCar car, int engineSize) => car.engine >= engine Size
- 你可以拥有不带参数的 Lambda 表达式。
() => Console.WriteLine("Parameter less lambda");
- 如果语句主体中有多个语句,它们应该用花括号括起来。
x => { Console.WriteLine("Few statements lambda"); return x*x; }
- 你可以在表达式主体中使用局部变量,该变量仅在你的 Lambda 表达式内部可见。
x =>
{
DateTime date = DateTime.Now;
return x + date.DayOfYear;
}
Lambda 中的变量作用域
在 Lambda 中,你可以引用在其定义的方法的作用域以及定义它们的类型的作用域中的变量。如果你将在 Lambda 的作用域中使用此类变量,它将被存储在 Lambda 的内存中,即使在其他情况下它会被垃圾回收。请看下面的示例,它演示了在定义 Lambda 的方法中,局部变量仍然在内存中并具有正确的值,尽管在通常情况下它不会起作用。
//declaring delegate: public delegate int SomeDelegate(int input); //implementing sample function public void ScopeExample() { //local variable that will be hold for delegate //even after ScopeExample is running out of the //scope and released from memory int j = 5; m_del = (x) => { Console.WriteLine("Input parameter is : " + x); Console.WriteLine("Local variable is: " + j); return j + x; }; int i = m_del(5); Console.WriteLine("Delegate internal result is: " + i); } //testing //delegates variable scope Program test = new Program(); test.ScopeExample(); int k = test.m_del(10); Console.WriteLine("Delegate external result is: " + k);
你可以下载附带的源代码进行尝试。
我从 MSDN 复制了有关 Lambda 表达式变量作用域规则如下:
- 已捕获的变量在引用它的委托超出范围之前不会被垃圾回收。
- 在 Lambda 表达式中引入的变量在外部方法中不可见。
- Lambda 表达式不能直接捕获封闭方法的 `ref` 或 `out` 参数。
- Lambda 表达式中的 `return` 语句不会导致封闭方法返回。
- Lambda 表达式不能包含 `goto` 语句、`break` 语句或 `continue` 语句,其目标位于 Lambda 主体外部或包含的匿名函数的主体内部。