这篇委托和事件的学习笔记是基于b站BeaverJoe大佬的视频学习整理的,老师主页还有很多案例教程可供学习~
BeaverJoe主页链接
委托是一种类,引用类型的数据类型。它有返回值类型和参数列表,可以指向一个或多个方法。
当实例化委托后(即创建了一个委托的实例),这个委托类型的实例可以和任何其他方法关联起来,存储这些方法(即 目标方法)的引用(前提是类型兼容),接着就可以通过委托类型的实例 间接调用这些存储的方法(invoke)
委托虽然是一种类,但它的声明格式却更接近于方法(这是因为仿造了c语言中函数指针的声明方式)。委托可以声明在类中,因为c#支持嵌套类,此时委托就是一个类的嵌套类。
【注1】可以省略写法(直接将方法赋值给委托类型变量):
//多播委托
myDelegate+=ChangeColor;
myDelegate+=Log;
【注2】封装的是有参数列表的方法的话,在调用委托时就要传入参数:
myDelegate01("James",3);
myDelegate01("Curry",3);
【注3】自定义声明委托时需要加上delegate关键词,但不是所有委托声明都需要使用delegate关键词,因为c#类库中已经准备好了两个委托Action和Func
可以省去声明委托类型的步骤,直接使用内置委托类型来声明创建变量。
【注】使用时要引入命名空间System
Action< T >: 指向返回值为空,参数列表为空或者有参数列表(最多16个参数)的方法
Func< T >:指向有返回值,参数列表为空或者有参数列表(最多16个参数)的方法,且返回值一定要放在<>中最后一位
(1)安全性上:即使可以通过多播委托来调用很多其他的方法,但也是很不安全的,如果把+=写成了=,就覆盖了之前的所有方法,提高了项目维护成本。
(2)关于内存泄漏:委托会引用一个方法,如果这个方法是一个实例方法(非静态方法)的话,那么这个方法就一定隶属于一个对象,拿一个委托引用这个方法,那么这个对象就必须存在在内存中,即使没有其他引用变量引用这个对象了,这个对象的内存也不能被释放。因为一旦释放内存,委托就不能再间接调用对象的方法了,所以说委托容易造成内存泄漏(memory leak),随着内存泄漏越来越多,程序就会面临崩溃。
(3)可读性上:如果在委托内部又嵌套了别的委托,debug难度就会上升,可读性也较差了。
未使用lambda表达式前:
使用lambda表达式:
lambda表达式真正的作用就是可以省去【创建方法】这一步骤,将Lambda表达式作为一个实际参数,传递到方法中
类可以作为方法的参数类型,而委托是一种类,所以可以把委托当作方法的参数,将指向的目标方法传递到另一个方法中去,就形成了一种动态调用方法的代码结构(根据不同的游戏需求,传递不同的委托类型封装的方法,达到动态调用的效果)。
而模板方法就是有一处不确定,其余代码都是确定好的,这个不确定的部分,就靠我们传进来的委托类型的参数所包含的这个方法来填补。
所谓的回调关系:某个方法,可以去调用它,也可以不去调用它,用得着的时候就去调用这个方法。
以回调方法形式使用【委托】,需要把委托类型作为参数传入到方法中,被传进方法的委托类型参数内部封装了一个被回调的方法,也就是回调方法,我们的方法会根据自己的逻辑来决定是不是要调用这个回调方法。
【注】一般回调方法都会放在方法代码的末尾之处,且一般回调方法都是没有返回值的
事件的定义:当某件有趣的事情发生时,一个类或者对象中的事件就会通知(关心这件事情的)别的类、别的对象“这里有件有趣的事情发生啦!”,别的类、别的对象就会纷纷做出各自的响应。
【注】事件必定是一个对象、类的成员,就像“死亡”必须基于“玩家”/“敌人”,“开花”必须基于“花”,“发布”必须基于“新游戏”一样,事件可以“发生”,核心功能是通知别人,发生效果。
事件模型包含5个步骤:
Step1:我(类)要有一个事件(成员)
Step2:一群别的类关心、订阅我的事件
Step3:我的事件发生了!
Step4:关心的类们被一次性通知到
Step5:被通知到的人,拿着事件参数,做出响应
A、事件的拥有者: 事件的源头,事件的拥有者一定是一个类(或者说是对象),谁拥有事件,谁就是事件的拥有者
B、事件: 一个类的成员类型,是一种让【事件的拥有者】具有通知能力的成员。事件不会主动发生,一定是由事件拥有者的内部逻辑触发
C、事件的响应者: 订阅事件的类、对象们
D、事件处理器: 通过通知和事件参数,而采取行动的行为称为“响应事件”或者“处理事件”,而处理事件时具体做的这些事情(事件响应者处理事件的方法成员),就叫做“事件处理器(EventHandler)”
【注】所谓的约定,就是指这个方法必须和声明事件的时候,会使用的委托类型相匹配(事件基于委托)
E、事件订阅(+=操作符): 事件处理器的【返回值】和【参数列表】必须和事件的【委托类型】保持一致
【注】事件有订阅,就一定有取消订阅,两者是配套出现的,以防内存泄漏的问题
自定义事件声明格式(完整 / 简略形式)
完整声明格式:
简略声明格式:(像委托类型字段但不是,事件的本质是委托类型字段的包装器,限制了外部对内部的委托字段的访问,只让访问+=和-=两个操作,而不能直接调用)
优化后:
【事件是基于委托的】有两层意思:
(1)类型兼容。 事件需要委托类型来做一个约束,这个约束既规定了事件它能发送什么样的消息给事件响应者,同时也规定了事件响应者能收到什么样的消息。事件处理器的【返回值】和【参数列表】必须和事件的【委托类型】保持一致,才能订阅这个事件
(2)存储方法的引用。 当事件的响应者向事件的拥有者提供了一个与之匹配事件的事件处理器之后,就需要一个地方,把这个事件处理器以及之后这些事件处理器保存、储存、记录下来,而只有委托类型的实例能办到这点。
Q:委托和事件的关系是什么?
A:事件不是特殊的委托类型字段,它只是委托类型字段的包装器,限制外界对委托类型的字段的访问,外界只能去访问他的+=、-=操作符(只能添加/移除事件处理器)
Q:为什么要使用委托类型来声明事件?(换句话说:为什么事件是基于委托的?)
A:当事件的响应者向事件的拥有者提供了一个与之匹配事件的事件处理器之后,就需要一个地方,把这个事件处理器以及之后这些事件处理器保存、储存、记录下来,而只有委托类型的实例能办到这点。