什么是委托
委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。 在实例化委托时,你可以将其实例与任何具有兼容参数和返回类型的方法进行绑定。 你可以通过委托实例调用方法。
简单的理解,委托是方法的抽象类,它定义了方法的类型,可以实例化。和普通的类一样,可以申明变量进行赋值,可以当作参数传递,可以定义成属性。
委托具有以下属性:
委托类似于 C++ 函数指针,但委托完全面向对象,不像 C++ 指针会记住函数,委托会同时封装对象实例和方法。
委托允许将方法作为参数进行传递。
委托可用于定义回调方法。
委托可以链接在一起;具备单播、多播功能。
方法不必与委托类型完全匹配。 有关详细信息,请参阅使用委托中的变体。
使用 Lambda 表达式可以更简练地编写内联代码块。 Lambda 表达式(在某些上下文中)可编译为委托类型。
1.委托基础介绍
1.1 delegate委托的声明
使用 delegate 关键字,定义具体的委托类型,Delegate至少0个参数,至多32个参数,可以无返回值,也可以指定返回值类型。
查看代码
方法绑定,进行调用
查看代码
1.2 Action 和 Func 背景
抽象的 Delegate 类提供用于松散耦合和调用的基础结构,但是这样看来,引发一个问题,无论何时需要不同的方法参数,这都会创建新的委托类型。 一段时间后此操作可能变得繁琐。 每个新功能都需要新的委托类型,幸运的是,没有必要这样做,框架已经帮我们定义Action 和 Func 类,我们可以直接申明进行使用
1.3 Action<T> 类
Action是无返回值的泛型委托。Action 委托的变体可包含多达 16 个参数,如 Action<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16>。 重要的是这些定义对每个委托参数使用不同的泛型参数,这样可以具有最大的灵活性。框架源码,如图:
使用就很方便了,我们只需要直接申明委托类型进行使用,例:
查看代码
1.4 Func<T> 类
Func 委托的变体可包含多达 16 个输入参数,如 Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,TResult>。 按照约定,返回结果的类型始终是所有 Func 声明中最后一个参数的类型,利用out类型参数实现。
Func是有返回值的泛型委托,func至少0个参数,至多16个参数,根据返回值泛型返回。必须有返回值,不可void。框架源码,如下:
使用就很方便了,我们只需要直接申明委托类型进行使用,例:
查看代码
2. 委托实战案例
我这里就做一个多播案例,帮助大家理解,其实.NET core 日志框架和其他第三方日志框架,差不多就是这种套路
2.1 定义Logger类
这个类我们的定义好委托和调用委托的方法。
查看代码
2.2 定义文件记录器
一个写入文件的,文件记录器
查看代码
2.3 定义数据库记录器
一个写入不同数据库的,数据库记录器
查看代码
以上两个代码逻辑,博主就不介绍了,就用一个控制台输出,代表业务代码了
2.4 测试
测试一下,广播和委托删除效果
查看代码
运行效果:
在实际项目中,大家就自行发挥
3. 委托变量捕获
3.1效果演示
说到委托,博主也把这个重要的知识点讲解一下,这个知识点很多人可能不知道或者踩过坑,但掌握了这个知识点其实可以实现一些比较花哨功能。
这里博主就用一个案例进行体现变量捕获,这里代码博主就用 lambda 表达式 进行简写,不太熟悉的可以通过链接跳转进行学习。
逻辑就是,简单的累计一下数量,通过最终的值体现。这里博主分别申明两个整数型变量,通过两个委托分别累计,然后看各自的值。两个委托区别就是传值方式的不同。
查看代码
测试效果:
大家发现没?逻辑代码一下,只是参数传递方式不一样,结果截然不同:
委托1的方式:不改变变量的值,方法之间是不共享这个参数的。这种很容易理解,就和我们调用普通方法一样,变量是值类型,是拷贝了一个副本传给了方法进行使用
委托2的方式:改变变量的值,方法之间是共享这个参数的。这种就像引用类型参数一样,是不是很神奇,难道是利用了ref关键字实现的?
3.2原理刨析
其实没有大家想学的那么神秘,委托之所以使用方式和类无异,是因为它本身就是一个类,只是这个过程的定义由编译器帮我们做了,我们只需要使用C#的语法糖。接下来博主就带大家揭开委托的神秘面纱。
我也给大家画一个简单的编译=》执行的过程
3.2.1 委托真实面貌
博主就简单写了一个委托,然后通过IL DASM工具查看IL代码
查看代码
3.2.2模拟委托调用过程
查看代码
大家发现没,最终的IL代码一模一样。也就说,委托就是编译器帮我们把func编译成一个带invoke函数的func类和生成一个装捕获的变量和函数体的类,然后通过构造函数将对象引用和函数指针(获取指针就是大家所说的把非托管指针压入当前栈)传给func类的实例化。然后最终调用的时候,委托类的invoke函数会去调用真正的函数。就这样完成了对函数的抽象。
3.2.3 委托变量生命周期
现在大家是不是对委托有了一定的理解了,而委托涉及到的捕获变量和参数变量,生命周期就说得通了,也知道为啥委托改变了变量,能通知到原本的变量,因为对变量就行了类的装箱,打包成了一个一个引用类型,那方法外部当然知道变量的值被改变了,因为大家都是拿着引用对象的地址呀。下面做个生命周期小总结:
p变量是普通变量,当方法被销毁时,它就会被销毁。
count2变量是捕获变量,当委托实例被销毁时,它才会被销毁。
4. 事件
其实讲完委托,事件就很容易理解了, 博主就简单讲解一下,如果大家有需要,博主就再写一篇详细的讲解。
事件:实际上,事件是建立在对委托的语言支持之上的一种设计而已。
4.1 事件定义语法
/定义一个委托
4publicdelegatevoiddelegateRun();
5//定义一个事件6publicevent delegateRun eventRun;
简单的说,事件可以看作是一个委托类型的变量
4.2委托和事件共性:
它们都提供了一个后期绑定方案:在该方案中,组件通过调用仅在运行时识别的方法进行通信。 它们都支持单个和多个订阅服务器方法。 也就是单播和多播支持。 二者均支持用于添加和删除处理程序的类似语法。 最后,引发事件和调用委托使用完全相同的方法调用语法。 它们甚至都支持与 ?. 运算符一起使用的相同的 Invoke() 方法语法。
4.3 事件原理刨析
publicevent EventHandler<NewMailEventArgs> NewMail;
可以看到当我们定义一个NewEvent时,编译器帮我们生成了:1. 一个private NewMail 字段,类型为 EventHandler<NewMailEventArgs>。 2.一个 add_NewMail 方法,用于将委托添加到委托链(内部调用了Delegate.Combine方法)。3.一个 remove_NewMail 方法,用于将委托从委托链移除(内部调用了Delegate.Remove方法)。对事件的操作,就是是对NewMail字段的操作。
4.4 如何选择
主要区别就是:
1.事件处理程序通过修改事件参数对象的属性将信息传回到事件源。 虽然这些惯用语可发挥作用,但它们不像从方法返回值那样自然。
2.包含事件的类以外的类只能添加和删除事件侦听器;只有包含事件的类才能调用事件。 事件通常是公共类成员。 相比之下,委托通常作为参数传递,并存储为私有类成员(如果它们全部存储)
3.当事件源将在很长一段时间内引发事件时,基于事件的设计会更加自然。比如基于事件的 UI 控件设计案例
总结:
(1)事件:事件时属于类的成员,所以要放在类的内部。
(2)委托:属于一个定义,是和类、接口类似的,通常放在外部。
所以事件这种架构设计思想还是很值得大家去学习的。
所以说,如果你的代码在不调用任何订阅服务器的情况下可完成其所有工作,使用基于事件的设计会更好点。
大家在项目中,怎么进行选择,就看实际需求了。