第5章委托、Lambda表达式与LINQ技术 在实际开发过程中,处理和操作数据是一项重要的任务。经常需要进行查询、筛选、排 序、转换,以及把方法作为参数传递等操作来满足特定的需求。为了更高效地处理这些操作 需求,委托、Lambda表达式、LINQ 就是常用的技术。本章就来学习这些技术的基础知识与 使用方法。 学习目标 (1)理解委托的含义。 (2)能利用委托解决实际问题。 (3)理解匿名方法。 (4)能在实际应用中运用Lambda表达式。 (5)理解LINQ 的含义。 (6)能根据需求编写各类LINQ 语句。 思政目标及设计建议 根据新时代软件工程师应该具备的基本素养,挖掘课程思政元素,有机融入教学中,本 章思政目标及设计建议如表5-1所示。 表5- 1 第5章思政目标及设计建议 思政目标思政元素及融入 培养学生学以致用的能力通过例子分析与实际操作培养学以致用的能力 培养自主探索、敬业、专注的工匠精神通过课前自主学习,培养自主探索、敬业、专注的工匠精神 5.1 委托的基本认识 前面的章节中定义过不少方法,大多数方法会定义一些形参,用于接收实际要传递的参 数,但是能不能把方法作为参数进行传递呢? 也就是定义形参时有没有一种数据类型的变 量可以接收方法? 答案是有的,这就是委托。也就是说,委托是一种数据类型,这种数据类型是用来接收 方法的。 ASP.NET 项目实战教程———从.NETFramework到.NETCore 1 06 官方给出的委托定义:委托是一种引用类型,表示对具有特定参数列表和返回类型的 方法的引用。 通俗地理解,委托相当于对具有相同返回类型和参数列表这一类方法进行了封装。 由于委托本质上也是一个派生自Delegate类的类,因此类可以声明在哪里,委托就可 以声明在哪里。 例1:委托的基本认识———定义一个能接收无参数、无返回值方法的委托。 打开VisualStudio2002,创建一个控制台应用(.NETFramework)程序,解决方案名称 为Delegate_Lambda_Linq,项目名称为DelegateDemo1,如图5-1所示。 图5-1 创建解决方案及项目 接下来定义委托,可是委托定义的位置在哪里呢? 因为委托本质也是类,因此要定义在 与类并列的位置。完整代码及注释如下。 namespace DelegateDemo1 { //(1)定义委托。即定义一个委托类型(委托是一种数据类型,能接收方法的数据类型), //用来保存无参数、无返回值的方法 //委托要定义在命名空间中,和类是同一个级别 public delegate void MyDelegate();//像定义抽象方法一样,没有实现(方法体) internal class Program { static void Main(string[] args) { //(3)使用委托:声明委托变量,并赋值 //声明了一个委托变量md,新建了一个委托对象,并且把方法M1 传递了进去 //即md 委托保存了M1 方法 //MyDelegate md =new MyDelegate(M1);//第1 种写法 MyDelegate md =M1;////第2 种写法 //(4)执行委托:调用md 委托时就相当于调用M1 方法 第5 章 委托、Lambda 表达式与LINQ 技术 1 07 md();//与下面等同 //md.Invoke();//Invoke()的作用是执行指定的委托 Console.WriteLine("ok"); Console.ReadKey(); } //(2)定义一个方法,由于该方法要传递给MyDelegate 委托,所以只能是无参数无返回 //值的方法 static void M1() { Console.WriteLine("我是一个没有参数没有返回值的方法"); } } } 以上代码首先定义了一个委托类型MyDelegate(),它是一个无参数无返回值的类型, 因此也只能接收无参数无返回值的方法。然后在Program 类中定义了一个方法,这个方法 是要传递给委托类型的,所以只能是无参数无返回值的方法。接着是使用委托,定义一个委 托类型变量,然后可以采用代码中的第1种写法或第2种写法,第2种写法更简洁。最后执 行委托,也有两种写法,第1种委托变量名(),第2种委托变量名.Invoke()。上面的代码执 行结果如图5-2所示。 图5-2 例1执行结果 提示:以上代码要注意书写位置,委托定义在哪里? 方法定义在哪里? 在哪里使用委托? 5.2 委托的基本应用举例 例2:假设一件事情在前面和后面要做的事情比较固定(这里假设输出“====== ==”),但是中间要做的事情经常发生变化(有可能是①要输出系统当前时间到控制台; ②要输出系统当前是星期几;③要把系统时间写入文本文件等)。 实现思路:定义一个方法,前后固定的动作用代码写好,中间的动作是不固定的,可以 用委托传递不同方法做不同的事情,因此定义的方法形参要是委托类型参数。具体实现步 骤和代码如下。 (1)在解决方案Delegate_Lambda_Linq下添加一个控制台应用(.NETFramework)新 项目,名称为DelegateDemo2。在DelegateDemo2项目下添加一个类TestClass,该类中编 写的代码如下。 //定义一个委托 public delegate void middleDelegate(); internal class TestClass { ASP.NET 项目实战教程———从.NETFramework到.NETCore 1 08 public void DoSomething(middleDelegate middleThing)//委托类型作为参数,即调用 //此方法要传递一个方法进来 { Console.WriteLine("=========================="); Console.WriteLine("=========================="); if (middleThing !=null) //委托是一个对象,就有可能为null,所以先判断下是否 //为null { middleThing(); //执行委托,根据传递的方法,执行得到不同效果 } Console.WriteLine("=========================="); Console.WriteLine("=========================="); } } 实例在TestClass类中定义了一个委托类型,然后定义了一个方法,该方法用定义的委 托类型作为形参。 (2)在Program.cs类中编写代码。首先根据需求定义三个不同的静态方法,其次在 Main()方法中编写测试代码,完整代码如下。 internal class Program { static void Main(string[] args) { TestClass tc =new TestClass(); //传递不同方法做不同的事情 tc.DoSomething(WriteTimeToFile); Console.WriteLine("OK"); Console.ReadKey(); } //把系统当前时间输出到控制台 public static void PrintTimeToConsole() { Console.WriteLine(System.DateTime.Now.ToString()); } //把系统当前时间输出到文件time.txt 中。time.txt 文件位置是指当前项目的可执 //行文件所在位置,即当前项目的bin\Debug 目录 public static void WriteTimeToFile() { //需要导入System.IO 命名空间 File.WriteAllText("time.txt", System.DateTime.Now.ToString()); } //把系统当前星期几输出到控制台 public static void PrintWeekToConsole() { Console.WriteLine(System.DateTime.Now.DayOfWeek.ToString()); } } 测试运行效果,可以看到向DoSomething()方法传递不同方法作为实参,可以实现执行 第5 章 委托、Lambda 表达式与LINQ 技术 1 09 不同的方法,产生不同效果。 提示:启动运行前,需要设置解决方案的启动项为当前选定内容。 例3:对字符串的处理经常要发生变化,例如,在字符串两端加“=”、加“★”,把字符串 字母全部转换为大写等。 实现思路:定义一个方法,该方法需要一个参数用于接收要处理的字符串,第二个参数 就是对字符串的处理方式,由于对字符串的处理方式是不固定的,所以第二个参数可定义为 委托类型。由于这里需要接收要处理的字符串,最后还要把处理后的字符串输出,所以定义 的委托有一个string类型的形参,返回值为string。 具体实现步骤和代码如下。 (1)在解决方案Delegate_Lambda_Linq下添加一个控制台应用(.NETFramework)新 项目,名称为DelegateDemo3。在DelegateDemo3项目下添加一个类TestClass,该类中编 写的代码如下。 namespace DelegateDemo3 { //定义一个委托(委托是一种数据类型,接收方法的数据类型) public delegate string GetStringDelegate(string str); internal class TestClass { public void ChangeStrings(string[] strs, GetStringDelegate GetString) { for (int i =0; i ———Action委托的泛型版本是一个无返回值,但是参数个数及 类型可以改变的委托。 第5 章 委托、Lambda 表达式与LINQ 技术 1 11 (3)Func———Func委托只有泛型版本的,接收的参数个数可以是若干个, 也可以是没有,但是一定有返回值的方法。 1.Action委托(非泛型版本) 例4:对例1改进。 在解决方案Delegate_Lambda_Linq下添加一个控制台应用(.NETFramework)新项 目,名称为DelegateDemo4。在Program.cs类中只需要编写如下代码。 internal class Program { static void Main(string[] args) { //Action 是内置委托,直接使用 Action md =M1; md(); Console.ReadKey(); } static void M1() { Console.WriteLine("我是一个没有参数没有返回值的方法"); } } 运行效果与例1一样。可以看到这里没有自己定义委托,而是直接使用内置委托 Action。 2.Action委托(泛型版本) 如何使委托能接收参数个数及类型都不固定的方法呢? ———使用泛型委托。 例5:自定义泛型委托。 假设方法的参数可以是string、int、bool三种数据类型,但方法均无返回值。如果不使 用泛型委托,那么就需要针对三种参数类型,定义三个不同的委托,分别用于接收三种类型 参数的方法,实例代码如下。 public delegate void Mydelegate1(string msg); public delegate void Mydelegate2(int i); public delegate void Mydelegate3(bool b); 那么如果参数类型有更多种呢? 这种定义显然很麻烦,怎么办呢? 这个时候就可以定义泛型委托。如何定义及使用呢? 下面通过例子来演示。 在解决方案Delegate_Lambda_Linq下添加一个控制台应用(.NETFramework)新项 目,名称为DelegateDemo5。在Program.cs类中编写如下代码。 namespace DelegateDemo5 { public delegate void MyGenericdelegate(T args);//这个委托就可以接收1 个参 //数、无返回值的方法,但是这个参数数据类型可以任意,这里一般用T 表示———这就是自定义的 //泛型委托 internal class Program { ASP.NET 项目实战教程———从.NETFramework到.NETCore 1 12 static void Main(string[] args) { MyGenericdelegate md1 =M1; md1("一个参数"); MyGenericdelegate md2 =M1; md2(1); MyGenericdelegate md3 =M1; md3(true); Console.ReadKey(); } //三个参数类型不同的方法可以定义为重载方法 static void M1(string msg) { Console.WriteLine(msg); } static void M1(int i) { Console.WriteLine(i); } static void M1(bool b) { Console.WriteLine(b); } } } 上面的加粗代码就是自定义的泛型委托,该泛型委托参数只能有1个,无返回值,但是 参数类型不固定,一般用T表示,而且委托名称后面要加。 对于这种自定义的泛型委托可以使用内置的泛型委托Action。也就是说,上面 的加粗代码可以注释或删除,然后把MyGenericdelegate换成Action 即可。除此之外, Action泛型委托参数不仅类型不固定,个数也可以不固定,也就是说,Action泛 型版本是一个无返回值,但是参数个数及类型可以改变的委托。 例6:Action泛型委托应用演示。 在解决方案Delegate_Lambda_Linq下添加一个控制台应用(.NETFramework)新项 目,名称为DelegateDemo6。在Program.cs类中编写如下代码。 static void Main(string[] args) { Action action1 =M1; action1("内置无返回值的泛型委托应用", 2);//两个参数 Action action2 =M1; action2(1); Action action3 =M1; action3(true); Console.ReadKey(); } static void M1(string msg,int i) { 第5 章 委托、Lambda 表达式与LINQ 技术 1 13 Console.WriteLine(msg+i); } static void M1(int msg) { Console.WriteLine(msg); } static void M1(bool b) { Console.WriteLine(b); } 内置委托Action和Action都不支持带返回值的方法,那么有没有内置的带返回 值的委托呢? 有,这就是Func,该委托只有泛型版本的,接收的参数个数可以是若干 个,也可以是没有,但是一定有返回值的方法。常见形式如下。 Func表示没有参数,只有返回值。 Func表示有一个参数,有返回值。 Func表示有两个参数(前两个参数T1,T2表示参数类型,最后的 TResult表示返回值类型),有返回值。 Func表示有三个参数(前三个参数T1,T2,T3表示参数类型, 最后的TResult表示返回值类型),有返回值。 总之,Func委托最后一个TResult表示返回值类型,前面的不管多少个T都是表示参数 类型。例 7:Func泛型委托应用演示。 在解决方案Delegate_Lambda_Linq下添加一个控制台应用(.NETFramework)新项 目,名称为DelegateDemo7。在Program.cs类中编写如下代码。 static void Main(string[] args) { #region 无参数有返回值的Func 委托 Func fun1 =M1; int n1 =fun1(); Console.WriteLine(n1); #endregion #region 有三个参数有返回值的Func 委托 Func fun2 =M1; int n2 =fun2(1, 2, 3); Console.WriteLine(n2); #endregion #region 有两个参数有返回值的Func 委托 Func fun3 =M1; string str =fun3("Func 委托应用", 2); Console.WriteLine(str); #endregion Console.ReadKey(); } static int M1() ASP.NET 项目实战教程———从.NETFramework到.NETCore 1 14 { return 1; } static int M1(int n1, int n2, int n3) { return n1 +n2 +n3; } static string M1(string msg, int i) { return msg+i; } 运行效果如图5-4所示。 图5-4 例7运行效果 5.4 多播委托 多播委托就是一个委托同时绑定多个方法,多播委托也叫委托链、委托组合。绑定方法 时也就是为多播委托变量多次赋值。除第一次赋值直接用“=”外,后面的赋值都可以用 “+=”,如果是解绑就用“-=”。 1.绑定无返回值的多个委托 例8:绑定无返回值的多个委托。 在解决方案Delegate_Lambda_Linq下添加一个控制台应用(.NETFramework)新项 目,名称为DelegateDemo8。在Program.cs类中编写如下代码,含义参见注释。 static void Main(string[] args) { #region 绑定无返回值的多个委托 Action action =M1;//这句话只绑定一个M1 方法(绑定第一个方法不 //能用+=复制,因为开始action 为null,所以只 //能用=赋值),下面再给action 绑定方法 action +=M2; //后面再为action 绑定方法时都可以用+= action +=M3; action +=M4; action +=M5; action -=M3; //解除绑定M3 方法(即用-=赋值为解除绑定方法) action("多播委托"); #endregion Console.ReadKey(); } static void M1(string msg) {