委托 和 事件
学习自B站up主
链接: 学习参考资料B站小Joe.
1、委托上-如何自定义委托
理解委托在现实世界的字面意思
委托是引用类型,是一种类,全称委托类型
typeof(delegate).IsClass 判定
委托创建的实例可以存储1个或N个方法,这个委托实例可以间接调用Invoke方法
类型兼容
委托的声明(与函数指针的声明类似),在创建实例,Target目标方法
target = new MyDelegate( target 目标方法)
myDelegate = target ;直接赋值
调用直接invoke
间接调用myDeleget(target方法)
单播委托 单个 = ,方法会覆盖掉上一个方法
( = 表示重置了委托封装的引用列表) 这是一个很大的缺点
多播委托 +=
target += new MyDelegate( target1)
target += new MyDelegate( target2)
【注意】
动态调用委托封装的方法
C#类库提供了2个委托的类型
Action委托和Func委托
2、委托下-Action和Func委托、委托的使用
委托类型的实例间接方法调用方法
使用委托当做参数传递到方法的用法,好处就是动态调用方法
泛型委托Action和Func,T泛化的数据类型
public delegate void MyDelegateAction();
public MyDelegateAction Action;
//上面两行等于下面一行
System.Action action; //没有参数列表
System.Action<String> action //有参数列表
Action和Func委托的区别,有无返回值
Func<in,in,in,out>
过度使用委托缺点:
1、委托是一种方法级别的耦合,这种耦合是违反设计模式的
2、使用委托不当的话,可读性下降,debug难度上升
3、如果你把模板方法、回调方法、异步调用、多线程纠缠在一起,代码维护就是灾难级别的
4、委托使用不当会导致内存泄露
内存泄露原理
委托引用一个方法,如果这个方法是一个实例方法(非静态),那这个方法必然隶属于一个对象,那个这个对象就必须存在在内存当中,无论有无其它引用,这个对象内存就不能被释放。所以一旦委托间接调用某一个方法,就会造成内存泄露。
【不要过度使用】
委托作为方法的参数
模板方法和回调方法
模板方法,就相当于对不确定的地方,用委托传入参数
回调方法,动态去选择,去调用的方法
匿名函数:lambda表达式,不用别处声明,一遍声明一遍调用,inline函数,只调用一次
3、泛型-泛化的数据类型
泛化,特化,类型参数
使用array
通过协程,bool值来禁止非常规操作
4、事件的定义和事件模型的5个组成部分
事件是一种特殊形式的委托类型的字段或变量
事件是对象/类型的成员
事件模型5个组成部分和5个步骤
事件参数列表可以没有参数
事件的拥有者:源头,一定是一个类!
事件:不会主动发生,由事件拥有者的内部逻辑触发,事件是一个工具,核心功能是通知,通知其他类对、象做出响应
事件解决了委托的问题,可以高内聚低耦合,只允许+=和-=,静止外部访问
事件的响应者:订阅事件的具体的类或对象
事件处理器:是处理事件的方法Method(此方法有限制),事件响应者的方法成员
订阅关系:+=和-=,要理解,解决了三个核心问题
类型兼容:事件处理器必须和事件相互匹配,遵循同一个规定,那就是委托类型
5、如何声明自定义的事件以及事件的完整/简略声明格式
事件的完整声明格式
Event事件是基于委托delegate的
1、类型兼容(委托约束)
2、存储方法的引用
委托是事件的底层基础
事件是委托的上层建筑
简略的声明格式,像字段的事件(语法糖)
在这里插入代码片
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EventEx : MonoBehaviour
{
//事件模型的5个组成部分
//事件拥有者【类】
//事件【event修饰】
//事件响应者【类】
//事件处理器【方法-受到约束】
//事件的订阅关系【+=】
Customer customer = new Customer();
Waitor waitor = new Waitor();
private void Start()
{
//事件是事件拥有者的Event成员
//订阅关系
customer.OnOrder += waitor.TakeAction; //先写方法名,然后,修复程序补完
//customer.OnOrder();//报错,不能再外部直接调用
customer.Order();//事件拥有者的内部逻辑,触发的事件
customer.PayBill();
}
}
//事件拥有者
public class Customer
{
public float Bill { get; set; }
public void PayBill()
{
Debug.Log("you have to pay the bill"+Bill);
}
//声明事件,点单的事件,完整声明格式
//蔚来就会去存储,去引用那些服务员的事件处理器
private OrderEventHandler orderEventHandler; //委托字段
//需要同时具有add和remove
public event OrderEventHandler OnOrder
{
add { orderEventHandler += value; } //添加事件处理器
remove { orderEventHandler -= value; }
}
//简略声明格式
//public event OrderEventHandler OnOrder;
public void Order()
{
if (orderEventHandler!=null) //如果事件有人注册
{
OrderEventArgs e = new OrderEventArgs();
e.CoffeeName = "Tingge";
e.CoffeeSize = "Big";
e.CoffeePrice = 22f;
orderEventHandler(this,e);
}
}
}
//事件响应者
public class Waitor
{
//自动修复的方法,完美匹配参数
//事件处理器,通过内部逻辑触发
internal void TakeAction(Customer _customer, OrderEventArgs _e)
{
float finalPrice = 0;
switch (_e.CoffeeSize)
{
case "Small":
finalPrice = _e.CoffeePrice;
break;
case "Middle":
finalPrice = _e.CoffeePrice+3;
break;
case "Big":
finalPrice = _e.CoffeePrice+6;
break;
}
_customer.Bill += finalPrice; //事件处理
}
}
//2个为了事件的前期准备
//为OnOrder事件声明委托,注意命名规范,事件+EventHandler,2个参数(事件拥有者,点餐的具体事件信息)
public delegate void OrderEventHandler(Customer _customer , OrderEventArgs _e);
//存储和传递事件参数,事件信息、事件消息,属于EventArgs这个类
public class OrderEventArgs : System.EventArgs
{
public string CoffeeName { get; set; }
public string CoffeeSize { get; set; }
public float CoffeePrice { get; set; }
}
事件:可以防止委托借刀杀人的情况发生
去掉event,答案还是正确,但是外界都可以访问它,也就是其他类对象可以访问,导致借刀杀人。
字段能做的,属性能做,属性能做的,字段不一定能做,保护字段
封装的概念
Q&A为什么要使用委托类型来声明事件
使用委托类型的实例去存储方法的引用,去存储这个事件处理器
事件响应者向事件拥有者,提供了一个与之匹配的事件的事件处理器后,存储事件处理器
链接: 学习参考资料B站小Joe.