乌啦呀哈呀哈乌啦!

欢迎光临,这里是喵pass的个人博客,希望有能帮到你的地方

0%

事件和委托

观察者模式

通过事件实现的观察者模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using UnityEngine;
using System;

public class Publisher : MonoBehaviour
{
// 声明一个事件
public static event Action OnEventPublished;

void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
// 发布事件
OnEventPublished?.Invoke();
}
}
}

public class Subscriber : MonoBehaviour
{
private void OnEnable()
{
// 订阅事件
Publisher.OnEventPublished += HandleEvent;
}

private void OnDisable()
{
// 取消订阅事件
Publisher.OnEventPublished -= HandleEvent;
}

// 事件处理方法
private void HandleEvent()
{
Debug.Log("Event received!");
}
}

委托

委托是个类,分为Delegate自定义委托类型,Func有返回值的委托类型,Action无返回值的委托类型

Func和Action的底层原理便是用Delegate声明一个委托类型(有返回值和无返回值),并且通过泛型参数(最多十六个)来实现自定义参数类型和参数
其中,Func委托类型的最后一个参数为返回值

委托需要先定义后使用

1
delegate void IntMethodInvoker(int x);

如上定义了一个委托InMethodInvoker,这个委托可以指向一个 int类型参数,返回值为void 的方法

Action委托 和 Func委托

Action委托引用了一个void返回类型的方法,T表示方法参数

1
2
3
4
Action
Action<in T>
Action<in t1,in t2>
Action<in t1,in t2,···,t16>

Func引用了一个带有一个返回值的方法,它可以传递0或者多到16个参数类型,和一个返回值类型

1
2
3
Func<out TResult>
Func<in t,out TResult>
Function<in t1,in t2,···,in t16,out TResult>

多播委托

前面是用的委托都只包含一个方法调用,但是委托也可以包含多个方法,这种委托叫做多播委托。使用多播委托可以按照顺序调用多个方法,多播委托只能得到调用的最后一个方法结果,一般我们把多播委托的返回值类型声明为void。
多播委托包含一个逐个调用的委托集合,如果通过委托调用的其中一个方法抛出异常,整个迭代就会停止。

使用匿名方法给委托赋值

前面使用委托都是先定义一个方法,然后把方法给委托的实例。但还有另外一种使用委托的方式,不用去定义一个方法,直接使用匿名方法(lambda expression)

1
2
3
4
5
6
7
8
9
10
Func<int,int,int> plus = delegate(int a, int b)
{
int temp = a+b;
return temp;
}
int res = plus(34,34)
Console.WriteLine(res);

//上述代码可以换成下面一行
Func<int, int, int> plus = (a, b) => { return a + b; };

事件

用event关键词修饰的字段,一种类型成员,有能力使一个类或者对象去通知其他类、对象们

事件是基于委托的,委托是事件的“底层基础”,事件是委托的“上层建筑”
委托类型定义了事件的有无返回值和参数类型,事件处理器必须和事件的有无返回值和参数类型一致,即双方都要遵守同一个约定(有无返回值和参数类型),我们把这叫做事件和事件处理器必须是匹配的

自定义事件

先声明该事件的委托类型,再声明事件

  • 在声明委托类型的时候,如果这个委托,是为了声明某个事件而准备的委托,那么这个委托的名字,就要去使用:事件名+EventHandler的格式,由于委托是一种引用类型,所以事件名首字母要大写
  • 在定义事件参数的时候,即在定义该事件的委托类型的参数的类型的时候,要遵循:类型名+EventArgs这个格式
事件声明示例
1
2
3
4
5
// 声明该事件的委托类型:事件名 + EventHandler
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);//声明一个委托类型

// 声明事件:访问修饰符 + event + 事件处理器(委托类型的实例、字段)+ 事件名称(一定要注意命名规范:On+事件名)
public event OrderEventHandler OnOrder;

微软提供了一个EventHandler委托类型

其中用来传递事件数据的类EventArgs,凡是用来传递事件数据的类,都是从这个类派生出来的
让自定义的传递事件数据的类继承EventArgs,就可以作为参数传入EventHandler委托类型了
将Object类型的变量转换为Customer类型的变量,我们可以用as操作符

1
2
3
public delegate void EventHandler(object? sender, EventArgs e);

Customer customer1 = _sender as Customer;

事件和委托的区别

  • 事件其实是委托类型字段的包装器、限制器,限制外界对委托类型字段的访问。
  • 外界只能通过“+=”和“-=”两个操作符对事件进行添加事件处理器和移除事件处理器的操作,并不能去赋值和触发事件。
  • 事件是用来阻挡非法操作的“蒙版”,它绝对不是委托字段的本身
  • 类似的情况有字段和属性,属性是字段的包装器。字段能做的,属性都能做;属性能做的,字段不一定都能做

总结:事件是用来“阉割”委托实例的,事件只能添加、删除事件处理器,不能赋值。外界只能用“+=”和“-=”去访问它,不能=,不能从外部触发事件,也就是说,事件包含了委托类型字段的所有功能,但只是对外部暴露了“+=”和“-=”操作符。

事件和委托的关系

委托类型规定了事件拥有者和事件响应者通知和接收的消息必须是同一类型的消息
约束了添加和移除事件时必须要使用与之匹配(同样类型)的事件处理器,即使用与之匹配(同样类型)的方法来处理响应这个事件

完整示例(顾客点单)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
/// <summary>
/// 事件的拥有者:顾客
/// 事件:点单
/// 事件的响应者:服务员
/// 事件处理器:计算最后金额
/// 事件订阅(+=操作符)
/// </summary>
namespace EventDemo
{
//声明一个委托类型
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);

class EventExample
{
public static Customer customer = new Customer();
public static Customer customer2 = new Customer();
public static Waiter waiter = new Waiter();
static void Main()
{
customer.OnOrder += waiter.CalculateBill;
customer2.OnOrder += waiter.CalculateBill;

//使用事件,事件只能由事件拥有者触发,不能在外部去触发
//顾客1点了超大杯摩卡15+6 = 21
customer.Order("摩卡", 15, OrderEventArgs.CoffeeSizeEnum.Venti);
//顾客1点了中杯拿铁20
customer.Order("拿铁", 20, OrderEventArgs.CoffeeSizeEnum.Tall);
//顾客2点了中杯卡布奇诺
customer2.Order("卡布奇诺", 25, OrderEventArgs.CoffeeSizeEnum.Tall);
customer.PayTheBill();
customer2.PayTheBill();

/*//如果使用委托,不使用事件,委托类型的字段可以在外部进行调用,意味着顾客2可以把自己点的东西记在顾客1的账单上
//顾客1点了超大杯摩卡15+6 = 21
OrderEventArgs e1 = new OrderEventArgs();
e1.CoffeeName = "摩卡";
e1.CoffeePrice = 15;
e1.CoffeeSize = OrderEventArgs.CoffeeSizeEnum.Venti;
customer.OnOrder(customer, e1);
//顾客1点了超大杯拿铁20+6 = 26,并记在了倒霉蛋顾客1的账单上
OrderEventArgs e2 = new OrderEventArgs();
e2.CoffeeName = "拿铁";
e2.CoffeePrice = 20;
e2.CoffeeSize = OrderEventArgs.CoffeeSizeEnum.Venti;
customer2.OnOrder(customer, e2);
customer.PayTheBill();
customer2.PayTheBill();*/


Console.Read();
}
}

public class Customer
{
//声明一个点单事件
public event OrderEventHandler OnOrder;

public float Bill { get; set; }
public void PayTheBill()
{
Console.WriteLine("I have to pay : " + Bill);
}

/// <summary>
/// 点餐(咖啡名称、价格、大小)
/// </summary>
/// <param name="coffeeName"></param>
/// <param name="coffeePrice"></param>
/// <param name="coffeeSize"></param>
public void Order(string coffeeName,float coffeePrice, OrderEventArgs.CoffeeSizeEnum coffeeSize)
{
//语法糖:如果事件不为空(因为简略声明事件时委托类型的字段被隐藏了)
if (OnOrder != null)
{
OrderEventArgs e = new OrderEventArgs();
e.CoffeeName = coffeeName;
e.CoffeePrice = coffeePrice;
e.CoffeeSize = coffeeSize;
//事件只能由事件拥有者触发:限制只能自己给自己点单
OnOrder(this, e);
}
}
}

public class Waiter
{
//计算账单金额
public void CalculateBill(Customer customer, OrderEventArgs e)
{
float finalPrice = 0;
switch (e.CoffeeSize)
{
case OrderEventArgs.CoffeeSizeEnum.Tall:
finalPrice = e.CoffeePrice; //中杯,原价
break;
case OrderEventArgs.CoffeeSizeEnum.Grand:
finalPrice = e.CoffeePrice + 3; //大杯:原价+3元
break;
case OrderEventArgs.CoffeeSizeEnum.Venti:
finalPrice = e.CoffeePrice + 6; //超大杯:原价+6元
break;
}
customer.Bill += finalPrice;
}
}

public class OrderEventArgs
{
// 咖啡是大杯、中杯还是小杯
public enum CoffeeSizeEnum { Tall,Grand,Venti}//默认为静态
public CoffeeSizeEnum CoffeeSize { get; set; }
// 咖啡价格
public float CoffeePrice { get; set; }
// 咖啡名称
public string CoffeeName { get; set; }
}
}

Credits

https://blog.csdn.net/Hotgun2222/article/details/139901041