C# 委托 (一)—— 委托、 泛型委托与Lambda表达式
C# 委托 (一)—— 委托、 泛型委托与Lambda表达式
目录
1 委托的含义
当需要将一个方法当作另一个方法的参数时,对于某些语言例如C/C++等,需要用函数指针来处理。而对于C#来说,则使用委托机制。
例如,当我们需要对一个泛型集合ICollection<T>进行排序时,我们定义一个Sort方法,那么这个方法需要哪些参数才能进行排序呢?首先,肯定需要一个Collection<T>对象作为输入参数,代表要排序的对象集合;然后,Sort方法还需要知道如何比较两个对象,经过比较之后才能决定让哪个对象排在前面。因此,Sort方法需要第二个参数,这个参数是一个方法,代表着比较排序对象的方法。这时候这个比较方法参数就只能是一个委托。我们在调用Sort方法对具体的类进行排序时,通过委托传入一个具体类的比较方法。
再比如,在使用LINQ时,我们经常会用到Where()、Find()等扩展方法,实现对集合中元素的筛选或查找。Where()方法拥有一个Func<T>参数,它就是一个委托。我们调用Where()时需要给这个委托传递一个方法,告诉Where筛选器筛选元素的规则方法。
委托是一种特殊的类,是一种能够引用方法的类。在创建委托时,就是创建了一个存储方法引用的对象。
委托是类型安全的。C函数指针只是一个指向一个存储单元的指针,不能保证指向的内容就是正确类型的函数。而对于C#的委托而言,声明一个委托时必须指定返回类型和参数,.NET编译器会严格检查方法的参数和返回类型和委托是否匹配,检查通过后才能进行转换。转换之后的委托实例作为一个参数,传递给调用它的函数。
一个委托可以被传递任何符合要求的方法。不同场合需要不同方法时,在调用的地方直接将委托参数替换为实际方法就行。因此,委托调用的方法是在程序运行时才能确定的。
2 委托声明、实例化和调用
2.1 委托的声明
前面提到过,委托是一种特殊的类,因此委托的声明与类的声明方法类似,在任何可以声明类的地方都可以声明委托。委托声明用delegate关键字,同时委托要指明方法参数和返回值,写法与方法类似。综合类的声明和方法的声明,委托声明写成如下形式:
[访问修饰符] delegate 返回值类型 委托名 (形参列表);
委托的声明实际上是定义了一个派生于System.Delegate类的类,这与一般类的声明语法不同。编译器会根据委托的声明自动创建一个委托的类并实现细节。
接下来我们以一个简单的List<Student>排序为例进行说明。假设我们有一个Student类,存放学生信息,拥有姓名、年龄和学号三个属性:
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public int Num { get; set; }
}
然后创建一个List<Student>:
Student s1 = new Student() { Name = "小红", Age = 10, Num = 1001 };
Student s2 = new Student() { Name = "小华", Age = 9, Num = 1002 };
List<Student> sList = new List<Student>();
sList.Add(s1);
List.Add(s2);
我们的目标是想给List<Student>对象添加一个排序方法,这个排序方法可以根据年龄或者学号来排序,具体需要哪一种排序需要在客户端调用时指定。(简单起见,本案例中List<Student>只包含两个元素,不纠结于排序算法)
按照要求,Student对象的比较有两种方法,我们实现两个比较方法,供委托使用:
//比较年龄
public static bool Younger(Student s1, Student s2) => s1.Age <= s2.Age;
//比较学号
public static bool NumSmaller(Student s1, Student s2) => s1.Num <= s2.Num;
由上,我们可以抽象出一个代表比较Student的方法的委托:
public delegate bool CompareDelegate(Student first, Student second);
这个委托的类名为CompareDelegate,注意到委托声明的返回值类型、参数与其代表的方法要完全一致。
2.2 委托的实例化
与普通类的使用方法相同,声明了委托之后,我们必须给委托传递一个具体的方法,才能在运行时调用委托实例。委托实例包含了被传递给它的方法的信息,在运行时,调用委托实例就相当于执行它当中的方法。
委托实例化格式如下:
委托类名 委托实例名 = new 委托类名(Target) ;
其中,委托实例名是自定义的名称,Target是要传入的方法的名称。注意,Target是方法的引用,不能带()。带()的话是该方法的调用。区分引用和调用。
委托的实例化还有一种简单的方法:
委托类名 委托实例名 = Target;
在需要委托实例的地方直接传入Target引用即可,C#编译器会自动根据委托类型进行验证,这称为“委托推断”。
案例:
//以下两种方法等价
CompareDelegate myCompareDelegate = new CompareDelegate(Younger);
CompareDelegate myCompareDelegate = Younger;//委托推断
2.3 委托实例的调用
委托实例等价于它当中实际方法,因此可以使用反射的Invoke()方法调用委托实例,也可以直接在委托实例后加上()进行调用。
我们下面看一下委托所代表的方法是如何被业务方法调用的。这里我们的业务是排序SortStudent方法:
//使用委托的业务方法
public static void SortStudent(List<Student> sList, CompareDelegate CompareMethod)
{
if (CompareMethod(sList[0], sList[1]))//等价于CompareMethod.Invoke(sList[0], List[1])
{
//sList[0]已经在sList[1]前面了,所以什么也不用做
}
else
{
sList.Reverse();//交换位置
}
//获取排名采用的比较方法的名称
Console.WriteLine($"\r\n按照 {CompareMethod.Method.Name} 排名:");
//打印排序后的链表
foreach (Student s in sList)
Console.WriteLine($"{s.Name} {s.Age} {s.Num} ");
}
这里Sort方法拥有一个CompareDelegate类型的委托实例CompareMethod,它可直接当做具体方法进行调用。
在客户端对委托进行实例化后,调用SortStudent()方法就可以进行排序了。
//委托的实例化与使用
CompareDelegate myCompareDelegate = NumSmaller;//采用比较学号的方法
SortStudent(sList, myCompareDelegate);
//使用委托推断,与上两行等价
SortStudent(sList, NumSmaller);
输出如下:
按照 NumSmaller 排名:
小红 10 1001
小华 9 1002
3 泛型委托
我们每次要使用一个委托时,都需要先声明这个委托类,规定参数和返回值类型,然后才能实例化、调用。为了简化这个过程, .NET 框架为我们封装了三个泛型委托类,因此大部分情况下我们不必再声明委托,可以拿来直接实例化使用,方便了我们的日常Coding。
.这三种泛型委托包括:Func<T>委托、Action<T>委托和Predicate<T>委托。
3.1 Func<T>委托
Func<T>委托代表着拥有返回值的泛型委托。Func<T>有一系列的重载,形式如 Func<T1,T2, ... TResult>,其中TResult代表委托的返回值类型,其余均是参数类型。只有一个T时,即Func<TResult>,代表该委托是无参数的。.NET封装了最多16个输入参数的Funct<>委托。
需要特别注意的是,若方法返回 void ,由于 void 不是数据类型,因此不能定义Func<void>委托。返回 void 的泛型委托见下文的Action<T>。
Func<T>的使用方法与一般的委托相同。例如上面的案例可改写如下;
public static void SortStudent(List<Student> sList,Func<Student,Student,bool> CompareMethod)
{
if(CompareMethod(sList[0], sList[1]))
{
}
else
{
sList.Reverse();
}
Console.WriteLine($"\r\n按照 {CompareMethod.Method.Name} 排名:");
foreach (Student s in sList)
Console.WriteLine($"{s.Name} {s.Age} {s.Num} ");
}
//客户端调用
Func<Student, Student, bool> myCompareFunc = NumSmaller;
SortStudent2(sList, myCompareFunc);
注意SortStudent2方法的委托参数也必须是Func<Student, Student, bool>,才能满足方法调用时类型一致的要求。
3.2 Action<T>委托
Action<T>委托代表返回值为空 void 的委托,它也有一些列重载,最多拥有16个输入参数。用法与Func<T>相同。
3.3 Predicate<T>委托
这个一般用的较少,它封装返回值为bool类型的委托,可被Func<T>代替。
4 匿名委托
采用匿名方法实例化的委托称为匿名委托。
每次实例化一个委托时,都需要事先定义一个委托所要调用的方法。为了简化这个流程,C# 2.0开始提供匿名方法来实例化委托。这样,我们在实例化委托时就可以 “随用随写” 它的实例方法。
使用的格式是:
委托类名 委托实例名 = delegate (args) {方法体代码} ;
这样就可以直接把方法写在实例化代码中,不必在另一个地方定义方法。当然,匿名委托不适合需要采用多个方法的委托的定义。
使用匿名方法,以上代码可改写为:
CompareDelegate anonymousCompare = delegate (Student s3, Student s4)
{
return s1.Num <= s2.Num;
};
SortStudent(sList, anonymousCompare);
需要说明的是,匿名方法并不是真的“没有名字”的,而是编译器为我们自动取一个名字。SortStudent方法打印了委托调用的方法的名字(见上文代码),我们可以看到如下输出:
按照 <Main>b__0 排名:
小红 10 1001
小华 9 1002
编译器为我们的匿名方法取了一个b__0的名字。
5 Lambda表达式
江山代有才人出,纵然匿名方法使用很方便,可惜她很快就成了过气网红,没能领多长时间的风骚。如今已经很少见到了,因为delegate关键字限制了她用途的扩展。自从C# 3.0开始,她就被Lambda表达式取代,而且Lambda表达式用起来更简单。Lambda表达式本质上是改进的匿名方法。
Lambda表达式的灵感可能是来源于数学中的函数表达式,例如下图:
Lambda表达式把其中的箭头用 => 符号表示。
如今Lambda表达式已经应用在很多地方了,例如方法体表达式(Expression-Bodied Methods)、自动只读属性表达式等等。
Lambda表达式形式上分为两种:
5.1 表达式Lambda
当匿名函数只有一行代码时,可采用这种形式。例如:
CompareDelegate LambdaCompare = (s4, s5) => s4.Age <= s5.Age;
其中=>符号代表Lambda表达式,它的左侧是参数,右侧是要返回或执行的语句。参数要放在圆括号中,若只有一个参数,为了方便起见可省略圆括号。有多个参数或者没有参数时,不可省略圆括号。
相比匿名函数,在表达式Lambda中,方法体的花括号{}和return关键字被省略掉了。
其实,上文定义NumSmaller()和Younger()方法时,由于这两个方法主体只有一行代码,所以用的也是表达式Lambda,这是Lambda表达式的推广, 是C# 6 编译器提供的一个语法糖。
5.2 语句Lambda
当匿名函数有多行代码时,只能采用语句Lambda。例如,上面的表达式Lambda可改写为语句Lambda:
CompareDelegate LambdaCompare = (s4, s5) =>
{
return s4.Age <= s5.Age;
};
语句Lambda不可以省略{}和return语句。
C# 委托 (一)—— 委托、 泛型委托与Lambda表达式的更多相关文章
- [C#学习教程-委托]001.大道至简之委托(代理),匿名函数,Lambda表达式
引言:此文翻译自CodeProject上的同名文章<C# Delegates,Anonymous Methods, and Lambda Expressions>,在此一起Mark一下,此 ...
- 委托-异步调用-泛型委托-匿名方法-Lambda表达式-事件【转】
1. 委托 From: http://www.cnblogs.com/daxnet/archive/2008/11/08/1687014.html 类是对象的抽象,而委托则可以看成是函数的抽象.一个委 ...
- C# 委托(delegate)、泛型委托和Lambda表达式
目录 # 什么是委托 # 委托声明.实例化和调用 1.声明 2.委托的实例化 3.委托实例的调用 4.委托完整的简单示例 #泛型委托 1.Func委托 2.Action委托 3.Predicate委托 ...
- c#委托中的匿名方法和lambda表达式
一.一般委托方式 Func<int, int, int> AddMethodHander; public unName() { AddMethodHander += AddMethod; ...
- 多播委托和匿名方法再加上Lambda表达式
多播委托就是好几个方法全都委托给一个委托变量 代码: namespace 委托 { class Program { static void math1() { Console.WriteLine(&q ...
- 委托、Action泛型委托、Func泛型委托、Predicate泛型委托的用法
一.举一委托场景:天气很热,二狗子想去买瓶冰镇可乐,但是二狗子很懒,于是他想找个人代他去,于是要有个代理人. 创建代理人之前先定义委托:public delegate string BuyColaDe ...
- c# 匿名方法(函数) 匿名委托 内置泛型委托 lamada
匿名方法:通过匿名委托 .lamada表达式定义的函数具体操作并复制给委托类型: 匿名委托:委托的一种简单化声明方式通过delegate关键字声明: 内置泛型委托:系统已经内置的委托类型主要是不带返回 ...
- C++ Primer : 第十章 : 泛型算法 之 lambda表达式和bind函数
一.lambda表达式 lambda表达式原型: [capture list] (parameter list) -> retrue type { function body } 一个lambd ...
- 委托学习笔记后续:泛型委托及委托中所涉及到匿名方法、Lambda表达式
引言: 最初学习c#时,感觉委托.事件这块很难,其中在学习的过程中还写了一篇学习笔记:委托.事件学习笔记.今天重新温故委托.事件,并且把最近学习到和委托相关的匿名方法.Lambda表达式及泛型委托记录 ...
- 泛型委托及委托中所涉及到匿名方法、Lambda表达式
泛型委托及委托中所涉及到匿名方法.Lambda表达式 引言: 最初学习c#时,感觉委托.事件这块很难,其中在学习的过程中还写了一篇学习笔记:委托.事件学习笔记.今天重新温故委托.事件,并且把最近学习到 ...
随机推荐
- Java位运算实现加减乘除四则运算
本文是继<一文了解有趣的位运算>的第二篇文章. 我们知道,计算机最基本的操作单元是字节(byte),一个字节由8个位(bit)组成,一个位只能存储一个0或1,其实也就是高低电平.无论多么复 ...
- jvm 内存溢出
堆内存溢出 堆内存中存在大量对象,这些对象都有被引用,当所有对象占用空间达到堆内存的最大值,就会出现内存溢出OutOfMemory:Java heap space 永久代溢出 类的一些信息,如类名.访 ...
- SpringCloud学习笔记(7):使用Spring Cloud Config配置中心
简介 Spring Cloud Config为分布式系统中的外部化配置提供了服务器端和客户端支持,服务器端统一管理所有配置文件,客户端在启动时从服务端获取配置信息.服务器端有多种配置方式,如将配置文件 ...
- .ssh/config 文件的解释算法及配置原则
前言 SSH 是连接远程主机最常用的方式,尽管连接到耽搁主机的基本操作非常直接,但当你开始使用大量的远程系统时,这就会成为笨重和复杂的任务. 幸运的是,OpenSSH 允许您提供自定义的客户端连接选项 ...
- jenkins自动化部署项目8 -- 新建job(服务代码部署在linux上)
jenkins(windows) ----> 应用服务器(linux): 1.后台java服务: 与部署在windows上不同的是,这里我选择了在[构建后操作]中使用ssh向远程linux服务器 ...
- commons-beanutils.jar使用问题
在导入这个工具包时候 容易存在错误 我总结了一下 有可能的几种错误情况 1.导入的包版本不对 commons-beanutils.jar和commons-logging.jar的版本不对 2.导入的包 ...
- 第八届蓝桥杯java b组第五题
标题:取数位 求1个整数的第k位数字有很多种方法.以下的方法就是一种. 对于题目中的测试数据,应该打印5. 请仔细分析源码,并补充划线部分所缺少的代码. 注意:只提交缺失的代码,不要填写任何已有内容或 ...
- JMeter 压测Server Agent无法监控资源问题,PerfMon Metrics Collector报Waiting for sample,Error loading results file - see file log, Can't accept UDP connections java.net.BindException: Address already in use 各种疑难杂症
如何安装插件此博主已经说得很详细了. https://www.cnblogs.com/saryli/p/6596647.html 但是需注意几点: 1.修改默认端口,这样可以避免掉一个问题.Serve ...
- CentOS8-网卡配置
一. 介绍 Centos8系统更新,新的版本让人看起来感觉很舒服,这时有人会配置CentOS8系统的网卡使系统上网,就会遇到配置好的网卡不会生效,自己想想和配置CentOS7的时候一个样啊,CentO ...
- Android蓝牙低功耗(BLE)模块设计
在阅读这篇文章之前你应该对GATT和Android蓝牙框架有一定的了解.这里不会向你解释Service.Characteristics等蓝牙知识.这里只是我写下我对Android Ble的再次封装来适 ...