C#_Events(事件_语法篇)
事件语法篇
本文内容
复习与回顾
- 什么是事件:C#语言中,事件是一种类型成员。
- 但凡是事件,必须隶属于某一个主体。
- 事件的核心是通知
- 事件是一种使对象或者类,能够提供通知的成员。
- 事件发生之后,关心这个事件的类/对象会被依次通知到,并根据[事件参数]作出响应。
- 事件模型
- 事件的拥有者
- 事件
- 事件响应者
- 事件处理器
- 事件订阅+=
- 解决了事件发生时通知谁
- 约束了事件响应者 => C#会进行一个严苛的检测
- 事件处理器必须和事件遵守同一个约定,而这种约定是使用委托类型的。这也是:事件是基于委托的
第一个案例
- 首先,先建一个2D的项目,之后创建一个脚本EventEx,把事件模型的五个组成部分,写出来:
using UnityEngine;
//事件模型的五个组成部分:
//事件的拥有者【类】
//事件【event】
//事件的响应者【类】
//事件的处理器【方法-受到约束的方法】
//订阅关系 【+=】
public class EventEx : MonoBehaviour{ }
- 事件的拥有者,谁拥有事件,谁就是事件的拥有者。
- 事件的拥有者一定是一个类(或者说对象)
- 事件的响应者也是同理,它也一定是一个对象或者说是一个类。
- 事件将会通过【关键字event】来进行修饰,告诉编译器我们将会在拥有者内部,声明这是一个事件。
- 事件处理器和事件的关系(本质就是事件处理器的返回参数和参表是否和事件的委托类型一致)
案例场景
点一份咖啡或者甜点,让顾客支付正确的金额。【点单event】
抽象成事件模型后的关系如下:
事件的拥有者 => 顾客
事件 => 点单
事件的响应者 => 服务员
事件处理器 => 计算最后的金额
事件订阅 =+
完整的声明格式
这里需要强调一下:事件基于委托是有2层含义的;
- 类型兼容
- 事件需要使用委托类型来做一个【约束】
- 这个事件既规定了事件可以发送什么样的消息给事件的响应者,也规定了事件的响应者能收到什么样的消息
- 这也间接规定了事件的响应者必须和这个约束匹配上,才能够订阅这个事件。
- 通过委托去储存方法
- 当事件的响应者向事件的拥有者提供一个与之匹配事件的事件处理器之后,我们需要一个地方进行存储,把这些事件处理器保存,储存,记录下来。
- 只有委托类型的实例可以做到。
- 总结:事件这种成员无论是从表层约束上来说还是从底层实现上来讲,都是依赖于委托类型的。
- 委托是事件的“底层基础”,事件是委托的“上层建筑”。
//事件平台里一个通用的委托
namespace System {
public delegate void EventHandler(object sender, EventArgs e);
//这里有2个参数,第一个参数sender,告诉我们,事件消息是由谁发送过来的,【就是事件的拥有者】
//第二个参数告诉我们,传递过来的事件的内容。
}
一个顾客,点了一杯中杯摩卡,应付28元。
using System;
using UnityEngine;
//事件模型的五个组成部分:
//事件的拥有者 Customer类
//事件 Onorder,点餐事件
//事件的响应者 Waiter类
//事件的处理器 方法-受到约束的方法 -> TakeAction方法
//订阅关系 +=
//声明委托类型,可以在类外也可以在类内形成一个【嵌套类】
//如果是为了声明事件,命名规则应为: 事件名 + EventHandler
public delegate void OrderEventHandler(Customer _customer, OrderEventArgs _e);//为OnOrder事件声明委托
public class EventEx : MonoBehaviour
{
Customer customer = new Customer();
Waiter waiter = new Waiter();
private void Start() {
customer.OnOrder += waiter.TakeAction;
customer.Order();//事件拥有者的内部逻辑,触发事件
customer.PayTheBill();
}
}
//事件的拥有者 : 顾客
public class Customer {
public float Bill { get; set; }
public void PayTheBill() {
Debug.Log("I have to pay :" + Bill);
}
//【点单事件】
// 事件基于委托有2层含义
//1. 类型兼容
//2. 通过委托去储存方法
private OrderEventHandler orderEventHandler;//委托类型的字段,储存事件处理器的
//声明事件
public event OrderEventHandler OnOrder {
//添加事件处理器
add {
//在不知道未来传进来的事件处理器是什么时,可以用value来表示。
orderEventHandler += value;
}
//移除事件处理器
remove {
orderEventHandler -= value;
}
}
public void Order() {
if(orderEventHandler != null) {
//委托类型字段,有两个参数
//第一个是Customer类型,表示事件的发送者是谁,这里就是它本身。
//第二个是事件参数,OrderEventArgs点餐的具体信息。
OrderEventArgs e = new OrderEventArgs();
e.CoffeName = "Mocha";
e.CoffeSize = "Tall";
e.CoffePrice = 28;
orderEventHandler(this, e);
}
}
}
//事件的响应者:服务员
public class Waiter {
internal void TakeAction(Customer _customer, OrderEventArgs _e) {
//根据不同的参数(_e)来计算不同的金额
float finalPrice = 0;
switch (_e.CoffeSize) {
case "Tall":
finalPrice = _e.CoffePrice;//中杯,原价
break;
case "Grand":
finalPrice = _e.CoffePrice + 3;//大杯,原价 + 3
break;
case "Venti":
finalPrice = _e.CoffePrice + 6;//超大杯,原价 + 6
break;
}
_customer.Bill += finalPrice;
}
}
//命名规则和事件一致
public class OrderEventArgs : EventArgs{
public string CoffeName { get; set; }
public string CoffeSize { get; set; }
public float CoffePrice { get; set; }
}
事件声明的完整格式时为了让大家更好的理解:事件和委托的关系。
事件绝对不是一个委托类型的字段。
简略的声明格式
在微软官方有关[事件模式]的定义当中,简略的声明格式中,他用了“Field-Like”的词,表示是像字段一样的声明格式
using System;
using UnityEngine;
//事件模型的五个组成部分:
//事件的拥有者 Customer类
//事件 Onorder,点餐事件
//事件的响应者 Waiter类
//事件的处理器 方法-受到约束的方法 -> TakeAction方法
//订阅关系 +=
//声明委托类型,可以在类外也可以在类内形成一个【嵌套类】
//如果是为了声明事件,命名规则应为: 事件名 + EventHandler
public delegate void OrderEventHandler(Customer _customer, OrderEventArgs _e);//为OnOrder事件声明委托
public class EventEx : MonoBehaviour
{
Customer customer = new Customer();
Waiter waiter = new Waiter();
private void Start() {
customer.OnOrder += waiter.TakeAction;
customer.Order();//事件拥有者的内部逻辑,触发事件
customer.PayTheBill();
}
}
//事件的拥有者 : 顾客
public class Customer {
public float Bill { get; set; }
public void PayTheBill() {
Debug.Log("I have to pay :" + Bill);
}
//【点单事件】
// 事件基于委托有2层含义
//1. 类型兼容
//2. 通过委托去储存方法
public event OrderEventHandler OnOrder;//事件的简略写法
public void Order() {
if(OnOrder != null) {
//委托类型字段,有两个参数
//第一个是Customer类型,表示事件的发送者是谁,这里就是它本身。
//第二个是事件参数,OrderEventArgs点餐的具体信息。
OrderEventArgs e = new OrderEventArgs();
e.CoffeName = "Mocha";
e.CoffeSize = "Tall";
e.CoffePrice = 28;
OnOrder(this, e);
}
}
}
//事件的响应者:服务员
public class Waiter {
internal void TakeAction(Customer _customer, OrderEventArgs _e) {
//根据不同的参数(_e)来计算不同的金额
float finalPrice = 0;
switch (_e.CoffeSize) {
case "Tall":
finalPrice = _e.CoffePrice;//中杯,原价
break;
case "Grand":
finalPrice = _e.CoffePrice + 3;//大杯,原价 + 3
break;
case "Venti":
finalPrice = _e.CoffePrice + 6;//超大杯,原价 + 6
break;
}
_customer.Bill += finalPrice;
}
}
//命名规则和事件一致
public class OrderEventArgs : EventArgs{
public string CoffeName { get; set; }
public string CoffeSize { get; set; }
public float CoffePrice { get; set; }
}
输出和上述一样。
这里我们就可以回答第一个问题: 事件是不是委托类型的字段?
答:像一个字段,但是无论如何去像,我们也不可能去成为另一方。在简略声明格式中,我们并没有去手动声明委托类型的字段,但是它实际上出现在了后台当中。
优化代码
using System;
using UnityEngine;
//事件模型的五个组成部分:
//事件的拥有者 Customer类
//事件 Onorder,点餐事件
//事件的响应者 Waiter类
//事件的处理器 方法-受到约束的方法 -> TakeAction方法
//订阅关系 +=
public class EventEx : MonoBehaviour
{
Customer customer = new Customer();
Waiter waiter = new Waiter();
private void Start() {
customer.OnOrder += waiter.TakeAction;
customer.Order("Mocha","Grand",32);//事件拥有者的内部逻辑,触发事件
customer.PayTheBill();
}
}
//事件的拥有者 : 顾客
public class Customer {
public float Bill { get; set; }
public void PayTheBill() {
Debug.Log("I have to pay :" + Bill);
}
//【点单事件】
// 事件基于委托有2层含义
//1. 类型兼容
//2. 通过委托去储存方法
public event EventHandler OnOrder;
//EventHandler的第一个参数是object类型,基类型,这也就意味着,无论我们传入什么样的类型进来都可以进行接收。
//第二个参数,凡是用来传递事件数据的类,它都是从EventArgs这个类型当中派生出来的
public void Order(string _name,string _size,float _price) {
if(OnOrder != null) {
//委托类型字段,有两个参数
//第一个是Customer类型,表示事件的发送者是谁,这里就是它本身。
//第二个是事件参数,OrderEventArgs点餐的具体信息。
OrderEventArgs e = new OrderEventArgs();
e.CoffeName = _name;
e.CoffeSize = _size;
e.CoffePrice = _price;
OnOrder(this, e);
}
}
}
//事件的响应者:服务员
public class Waiter {
internal void TakeAction(object _sender, EventArgs _e) {
//根据不同的参数(_e)来计算不同的金额
float finalPrice = 0;
Customer customer = _sender as Customer;
OrderEventArgs e = _e as OrderEventArgs;
switch (e.CoffeSize) {
case "Tall":
finalPrice = e.CoffePrice;//中杯,原价
break;
case "Grand":
finalPrice = e.CoffePrice + 3;//大杯,原价 + 3
break;
case "Venti":
finalPrice = e.CoffePrice + 6;//超大杯,原价 + 6
break;
}
customer.Bill += finalPrice;
}
}
//命名规则和事件一致
public class OrderEventArgs : EventArgs{
public string CoffeName { get; set; }
public string CoffeSize { get; set; }
public float CoffePrice { get; set; }
}
为什么要使用事件,事件成员可以让程序之间的逻辑和对象之间的关系,变的非常的安全。可以防止“借刀杀人”的情况发生。
以此案例为主,我们可以删除事件的event,你会发现脚本并无报错,但是一个public的委托类型的字段,是相当危险的,如果我们在此实例的基础上再new一个新的顾客进行购买,但是费用确是第一个顾客进行支付。
using System;
using UnityEngine;
//事件模型的五个组成部分:
//事件的拥有者 Customer类
//事件 Onorder,点餐事件
//事件的响应者 Waiter类
//事件的处理器 方法-受到约束的方法 -> TakeAction方法
//订阅关系 +=
public class EventEx : MonoBehaviour
{
Customer customer = new Customer();
Customer GoodBro = new Customer();
Waiter waiter = new Waiter();
private void Start() {
customer.OnOrder += waiter.TakeAction;
GoodBro.OnOrder += waiter.TakeAction;
OrderEventArgs e1 = new OrderEventArgs();
e1.CoffeName = "Mocha";
e1.CoffeSize = "Tall";
e1.CoffePrice = 30;
customer.OnOrder(customer, e1);
OrderEventArgs e2 = new OrderEventArgs();
e2.CoffeName = "Mocha";
e2.CoffeSize = "Tall";
e2.CoffePrice = 30;
GoodBro.OnOrder(customer, e2);
customer.PayTheBill();
GoodBro.PayTheBill();
}
}
//事件的拥有者 : 顾客
public class Customer {
public float Bill { get; set; }
public void PayTheBill() {
Debug.Log("I have to pay :" + Bill);
}
//【点单事件】
// 事件基于委托有2层含义
//1. 类型兼容
//2. 通过委托去储存方法
public EventHandler OnOrder;
//EventHandler的第一个参数是object类型,基类型,这也就意味着,无论我们传入什么样的类型进来都可以进行接收。
//第二个参数,凡是用来传递事件数据的类,它都是从EventArgs这个类型当中派生出来的
public void Order(string _name,string _size,float _price) {
if(OnOrder != null) {
//委托类型字段,有两个参数
//第一个是Customer类型,表示事件的发送者是谁,这里就是它本身。
//第二个是事件参数,OrderEventArgs点餐的具体信息。
OrderEventArgs e = new OrderEventArgs();
e.CoffeName = _name;
e.CoffeSize = _size;
e.CoffePrice = _price;
OnOrder(this, e);
}
}
}
//事件的响应者:服务员
public class Waiter {
internal void TakeAction(object _sender, EventArgs _e) {
//根据不同的参数(_e)来计算不同的金额
float finalPrice = 0;
Customer customer = _sender as Customer;
OrderEventArgs e = _e as OrderEventArgs;
switch (e.CoffeSize) {
case "Tall":
finalPrice = e.CoffePrice;//中杯,原价
break;
case "Grand":
finalPrice = e.CoffePrice + 3;//大杯,原价 + 3
break;
case "Venti":
finalPrice = e.CoffePrice + 6;//超大杯,原价 + 6
break;
}
customer.Bill += finalPrice;
}
}
//命名规则和事件一致
public class OrderEventArgs : EventArgs{
public string CoffeName { get; set; }
public string CoffeSize { get; set; }
public float CoffePrice { get; set; }
}
总结:为了防止委托在外部被滥用,微软推出了【事件】这种成员。
事件和属性的类比
- 事件“包裹”了一个委托类型字段,而属性包裹了字段。
- 属性绝对不是字段,事件也是同理。他们在这里都起到了保护的作用。保护委托类型的字段不被外界滥用。
- 包装器永远不可能是被包装的东西的本身。
- 事件的本质就是一个委托类型字段的包装器。【限制委托类型的访问,要想访问只能+=和-=】
- 可以说成:事件“封装”委托实例。【对外界隐藏了委托实例的大部分内容,仅仅暴露了添加和移除事件处理器的功能】
回答最开始的问题
- 委托和事件的关系
- 事件不是一种特殊委托类型的字段,因为事件的简略声明格式让人误以为误以为是一种特殊的委托,但是文档中,它仅仅只是“Field-Like”。
- 无论它再怎么像委托类型的字段,他都不是委托类型的字段。在简略声明当中,我们应该注意public后面的event,它表明了它就是一个事件。
- 它只是委托类型字段的包装器,限制器,限制外界对委托类型的字段的访问。
- 为什么要使用委托类型来声明事件
- 站在事件的拥有者,为了表明事件拥有者,能够对外部通知什么样的消息。
- 站在事件的响应者,是一种约定事件处理器能够收到什么样的消息,也约束了我们使用什么样的方法。
- 并且我们会去使用委托类型的实例去存储方法的引用,储存未来这个事件处理器。
- 能够存储这些东西,只有委托类型可以办到。