C#进阶之全面解析Lambda表达式
引言
在实际的项目中遇到一个问题,我们经常在网上搜索复制粘贴,其中有些代码看着非常的简洁,比如Lambda表达式,但是一直没有去深入了解它的由来,以及具体的使用方法,所以在使用的时候比较模糊,其次,编程涉及面比较广,我们不可能每个方面都去精通了解,但经常运到的一些东西,必须了解其具体使用方法及使用场景,才能书写出优美、简洁、可读性强的代码。笔者通过搜索、整理资料及测试代码,详细的介绍Lambda 表达式的用法。
Lambda 表达式概念
“Lambda 表达式”(lambda expression)是一个匿名函数,可以表示为委托的代码,或者表示为表达式树的代码,它所表示的表达式树可以编译为委托。 Lambda 表达式的特定委托类型取决于其参数和返回值。不返回值的 Lambda 表达式对应于 Action
委托,具体取决于其参数数量。 返回值的 Lambda 表达式对应于 Func
委托,具体取决于其参数数量。
Lambda 表达式广泛用于:
-
将要执行的代码传递给异步方法,例如 Task.Run(Action)。
-
编写 LINQ 查询表达式。
-
创建表达式树。
C# 中委托的演变
在 C# 1.0 中,通过使用在代码中其他位置定义的方法显式初始化委托来创建委托的实例。 C# 2.0 引入了匿名方法的概念,作为一种编写可在委托调用中执行的未命名内联语句块的方式。 C# 3.0 引入了 Lambda 表达式,这种表达式与匿名方法的概念类似,但更具表现力并且更简练。 这两个功能统称为匿名函数。 通常,面向 .NET Framework 3.5 及更高版本的应用程序应使用 lambda 表达式。
C#1.0中委托的实现,代码如下:
delegate int CalculateHandler(int x, int y); private int Sum(int x, int y) { return x + y; } public void Test() { CalculateHandler sumHandler =new CalculateHandler(Sum); MessageBox.Show(sumHandler(1, 2).ToString());//输入结果3
}
C#2.0中匿名方法的实现,代码如下:
delegate int CalculateHandler(int x, int y); public void Test() { CalculateHandler sumHandler = delegate(int x, int y) { return x + y; }; MessageBox.Show(sumHandler(1, 2).ToString());//输入结果3 }
C#3.0中Lambda 表达式的实现,代码如下:
delegate int CalculateHandler(int x, int y); public void Test() { CalculateHandler sumHandler = (x, y) => x + y; MessageBox.Show(sumHandler(1, 2).ToString());//输入结果3 }
由此可以看出微软的一步步升级,带给我们的是编程上的优美,简洁,可读性强,因此作为程序员我们要一直处于学习的路上。
Lambda 表达式使用
C#的Lambda 表达式都使用 Lambda 运算符 =>,该运算符读为“goes to”, 若要创建 Lambda 表达式,需要在 lambda 运算符左侧指定输入参数(如果有),然后在另一侧输入表达式或语句块。 例如,单行 Lambda 表达式 x => x * x
指定名为 x
的参数并返回 x
的平方值。
在介绍Lambda 表达式使用之前我们先了解.Net为我们定义好的Action<T>和Func<T>两个泛型委托。
Action<T>泛型委托
Action<T>委托表示引用一个返回类型为Void的方法。这个委托存在不同的变体,可以传递之多16个不同的参数类型。同时,没有泛型参数的Action类可以调用没有参数的方法。例如,Action<in T>表示有一个输入参数的方法,Action<in T1,in T2>表示有两个输入参数的方法。
Func<T>泛型委托
Func<T>可以以类似的方法使用。不过Func<T>允许调用带返回参数的方法。Func<T>也有不同的变体,之多可以传递16个参数和一个返回类型。例如:Func<out TResult>委托类型可以无参的带返回类型的方法,Func<in T1,inT2,out Tresult>表示带两个参数和一个返回类型的方法。
Func<T>可以表示带输出的方法,T可以有多个,且只有最后一个表示输出即最后一个是返回类型。Func<in T1,inT2,out Tresult>中的字符in、out在实际代码中是不会出现的。
表达式 Lambda
表达式位于 =>
运算符右侧的 Lambda 表达式称为“表达式 lambda”。具体形式:(input-parameters) => expression,表达式 lambda 会返回表达式的结果。
具体事例,代码如下:
public Action SuccessPrompt =() => MessageBox.Show("执行成功!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); //没有参数,括号不能省略 public Func<int, int, bool> Compare = (x, y) => x > y;//判断x是否大于y
语句 Lambda
语句 Lambda 与表达式 lambda 表达式类似,只是语句括在大括号中,具体形式:(input-parameters) => { statement; }。语句 lambda 的主体可以包含任意数量的语句;但是,实际上通常不会多于两个或三个。
具体事例代码如下:
public Action<string> Prompt = prompt => { MessageBox.Show(prompt, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); };//仅当 Lambda 只有一个输入参数时,括号才是可选的;否则括号是必需的
异步 Lambda
通过使用 async 和 await 关键字,你可以轻松创建包含异步处理的 lambda 表达式和语句。其中async
和 await
关键字是在 C# 5 中引入的。
await关键字
await
运算符应用于异步方法中的任务,在方法的执行中插入挂起点,直到所等待的任务完成。 任务表示正在进行的工作。
await
仅可用于由 async 关键字修改的异步方法中。 使用 async
修饰符定义并且通常包含一个或多个 await
表达式的这类方法称为异步方法。
async修饰符
使用 async
修饰符可将方法、lambda 表达式或匿名方法指定为异步。 如果对方法或表达式使用此修饰符,则其称为异步方法。
使用异步 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);//Task.Delay方法只会延缓异步方法中后续部分执行时间,当程序执行到await表达时,一方面会立即返回调用方法,执行调用方法中的剩余部分,这一部分程序的执行不会延长。另一方面根据Delay()方法中的参数,延时对异步方法中后续部分的执行。 } }
Lambda 表达式和元组
自 C# 7.0(对应 .NET Framework4.7和Visual Studio 2017 )起,C# 语言提供对元组的内置支持。 可以提供一个元组作为 Lambda 表达式的参数,同时 Lambda 表达式也可以返回元组。 在某些情况下,C# 编译器使用类型推理来确定元组组件的类型。可通过用括号括住用逗号分隔的组件列表来定义元组,通常,元组字段命名为 Item1
、Item2
等等。但是,可以使用命名组件定义元组。
事例代码如下:
public void Test1() { Func<(int, int, int), (int, int, int)> doubleItem = ns => (2 * ns.Item1, 2 * ns.Item2, 2 * ns.Item3); var itemList = (1, 2, 3); var resultDItemList = doubleItem(itemList);//结果为[2, 4, 6] } public void Test2() { Func<(int x, int y, int z), (int, int, int)> doubleItem = ns => (2 * ns.x, 2 * ns.y, 2 * ns.z); var itemList = (1, 2, 3); var resultDItemList = doubleItem(itemList);//结果为[2, 4, 6] }
含标准查询运算符的 Lambda
在其他实现中,LINQ to Objects 有一个输入参数,其类型是泛型委托 Func<TResult> 系列中的一种。 这些委托使用类型参数来定义输入参数的数量和类型,以及委托的返回类型。 Func
委托对于封装用户定义的表达式非常有用,这些表达式将应用于一组源数据中的每个元素。
标准查询运算符是组成 LINQ 模式的方法。 这些方法中的大多数都作用于序列;其中序列指其类型实现 IEnumerable<T> 接口或 IQueryable<T> 接口的对象。 标准查询运算符提供包括筛选、投影、聚合、排序等在内的查询功能。
共有两组 LINQ 标准查询运算符,一组作用于类型 IEnumerable<T> 的对象,另一组作用于类型 IQueryable<T> 的对象。 构成每个集合的方法分别是 Enumerable 和 Queryable 类的静态成员。 这些方法被定义为作为方法运行目标的类型的扩展方法。 这意味着可以使用静态方法语法或实例方法语法来调用它们。
具体事例代码如下:
public class Sutdent { public string Id { get; set; } public string Name { get; set; } public int Age { get; set; } } static void Main(string[] args) { List<Sutdent> studentList = new List<Sutdent>(); studentList.Add(new Sutdent {Id = "001", Name = "张三", Age = 18}); studentList.Add(new Sutdent {Id = "002", Name = "李四", Age = 19}); studentList.Add(new Sutdent {Id = "003", Name = "王五", Age = 16}); studentList.Add(new Sutdent {Id = "004", Name = "赵六", Age = 17}); List<Sutdent> list1 = studentList.FindAll(st => st.Age > 17);//选择年龄大于17的所有学生 List<Sutdent> list2 = studentList.Where(st => st.Age > 17).ToList();//选择年龄大于17的所有学生 studentList.Sort((st1,st2)=>st2.Age-st1.Age);//按Age降序排列 List<string> list3 = studentList.Select(st => st.Name).ToList();//选择列表中的所有名字 }
Lambda 表达式中的类型推理
编写 Lambda 时,通常不必为输入参数指定类型,因为编译器可以根据 Lambda 主体、参数类型以及 C# 语言规范中描述的其他因素来推断类型。
lambda 类型推理的一般规则如下:
-
Lambda 包含的参数数量必须与委托类型包含的参数数量相同。
-
Lambda 中的每个输入参数必须都能够隐式转换为其对应的委托参数。
-
Lambda 的返回值(如果有)必须能够隐式转换为委托的返回类型。
总结
通过上边的讲解,我们可以看出Lambda表达式的用法非常的简单,特别在标准查询运算符中应用非常广泛,提高了编程效率,且写出的代码非常的简洁。文中若有不足之处,还望海涵,博文写作不易希望多多支持,后续会更新更多内容,感兴趣的朋友可以加关注,欢迎留言交流!