【Unity&&C#】委托与事件笔记

委托

委托的定义

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

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

委托的两种使用情况:

  • 模板方法(一般是用有返回值的Func)

就比如写作文的作文模板,同一个作文模板,通过修改其中的某些需要自己填写的句子,可以套用到不同的作文题目中。

在程序世界中,通过将委托类型的参数当作形参传入到函数中,通过将不同的函数赋给该委托类型作为形参,来实现调用该委托类型下的不同的函数方法,从而实现动态调用代码

传入该委托类型的形参便是现实世界中作文模板里某些需要自己填写的句子,模板方法当中,就只有传入该委托类型的形参是不确定的

当发生以上这种情况时,好几个函数只有一行代码(计算哪个数据的最大值)是不同的,其他逻辑都一样的情况,按照一般的写法,要用多少个函数,就得写多少个函数,很麻烦而且不利于代码复用,如果用委托的模板方法的话,只需要传入不同的函数赋给委托类型的形参,就可以实现不同函数的效果,增加或减少功能不用去修改到GetTopNum函数,只需要去增加一个小方法,提高了代码的复用性,减少了Bug

(将函数作为委托类型的形参传入的意思是将该函数赋值给该委托类型)

如果写成LAMBDA表达式的话,甚至都不用去增加一个小方法,只需要赋给委托类型一个匿名函数(不用写方法名的函数)即可

参数不用写类型是因为给LAMBDA表达式作委托类型的参数的时候,委托类型会自动进行类型推断

  • 回调方法(一般是用无返回值的Action)

有这么一种方法,在想要调用的时候调用,在不想要调用的时候就不调用

事件

一、事件定义

事件是一种类型成员,事件有能力使一个类或者对象去通知其他类、对象们

事件是基于委托的,委托是事件的“底层基础”,事件是委托的“上层建筑”

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

二、事件模型的组成部分

事件的拥有者:闹钟【类】             声明事件所在的类

事件:闹钟响了【event关键字修饰】    OnXXX

事件的响应者:舍友【类】             拥有具体方法XXX

事件处理器:起床【方法——受到约束的方法】   XXXEventHandler

事件订阅(+=操作符):舍友订阅了自己的闹钟响铃的事件【+=】  事件的响应者订阅事件

和委托相比的好处:

事件右边禁止使用“=”操作符,避免了使用多播委托的时候不小心将“+=”操作符写成“=”操作符的问题,避免了不小心重置了委托封装的引用列表的问题

事件的好处还有一点就是事件不能够在外部直接触发 customer.OnOrder(); ×

三、自定义事件

(1)声明该事件的委托类型:

在声明委托类型的时候,如果这个委托,是为了声明某个事件而准备的委托,那么这个委托的名字,就要去使用:事件名+EventHandler的格式,由于委托是一种引用类型,所以事件名首字母要大写

在定义事件参数的时候,即在定义该事件的委托类型的参数的类型的时候,要遵循:类型名+EventArgs这个格式

(2)声明事件:

声明事件的完整格式(用于理解事件内部结构、事件和委托的关系):

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

面试官如果问你:事件是不是委托类型的字段?是不是一种特殊类型的委托类型的字段呢?

一定要回答:不是不是

事件和委托就像妹妹和姐姐的关系,姐姐再像妹妹,她也不是妹妹

事件是委托类型字段的包装器,保护委托类型的字段不被外界所滥用,外界只能用“+=”和“-=”去访问它,也就是说,事件包含了委托类型字段的所有功能,但只是对外部暴露了“+=”和“-=”操作符。

总结:事件是用来“阉割”委托实例的,事件只能add、remove事件处理器,不能赋值。并且事件只能+=、-=,不能=、不能外部触发事件

类似的情况有字段和属性,属性是字段的包装器。字段能做的,属性都能做;属性能做的,字段不一定都能做

声明事件的简略格式(常用):

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

public event OrderEventHandler OnOrder;

简略声明事件后,我们并没有手动声明该事件的委托类型的字段,但实际上,后台帮我们声明了这个字段,用来存储事件处理器的引用

(3)触发事件(只能由事件拥有者触发)

使用声明事件的简略格式声明事件后,触发事件:

其中,OnOrder!=null是事件后不跟“+=”或“-=”操作符的一种特殊情况,因为使用声明事件的简略格式声明事件后,我们并没有声明该事件的委托类型的字段(其实是被隐藏起来了),无法判断这个字段是否为空,所以只能使用语法糖用事件OnOrder取代原先这个位置上的委托类型的字段(orderEventHandler),来判断事件是否为空

语法糖是什么:

  • 微软提供的委托类型 EventHandler

微软提供的用来传递事件数据的类EventArgs,

凡是用来传递事件数据的类,都是从这个类派生出来的

让自定义的传递事件数据的类继承EventArgs,就可以作为参数传入EventHandler委托类型了

将Object类型的变量转换为Customer类型的变量,我们可以用as操作符

  • 面试题

(1)委托和事件的关系?

很多人都误会了委托和事件的关系,认为事件是委托类型的字段,这样是错误的。

这个误会的来源,一是很多书上只是一笔带过事件的简略声明格式,并没有去介绍事件声明的内部结构(一个添加事件处理器,一个移除事件处理器);二是很多人把官方文档中对事件的定义(看上去像一个委托类型的字段),当作是事件是委托类型的一个字段的意思;三是在添加或移除事件处理器的时候,用的是+=或-=,后面跟的是方法的名字,但其实我们也可以写成 “事件+= new 事件处理器(方法)”的格式,跟委托类型的字段去添加方法的引用是一样的,会让人误以为事件也是委托类型的字段,再加上判断事件是否为空时,我们使用的语法糖if(事件!=null)更会加深误会

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

(2)为什么要使用委托类型来声明事件?为什么说事件是基于委托的?

视频中讲解的:

站在事件拥有者的角度来说,是为了表明事件拥有者,能够对外部通知什么样的消息;如果站在事件的订阅者、被通知的这个人的角度来看,是一种约定,事件处理器能够收到什么样的消息,也约束了我们使用什么样的方法,什么样的签名,来处理响应这个事件。并且,我们用委托类型的字段(委托类型的实例)可以去储存方法的引用,去储存事件处理器,当事件的响应者,向事件的拥有者,提供了一个与之匹配的事件的事件处理器之后,我们需要把这个事件处理器保存到某一地方。而能够记录、引用方法的这个任务,也只有委托类型的实例才能做到。所以我们在声明自定义事件的时候,使用的是委托类型。

自己总结的:

委托类型规定了事件拥有者和事件响应者通知和接收的消息必须是同一类型的消息,约束了添加和移除事件时必须要使用与之匹配(同样类型)的事件处理器,即使用与之匹配(同样类型)的方法来处理响应这个事件;并且事件响应者向事件拥有者提供了一个与之匹配的事件处理器之后,需要使用委托类型的字段(实例)来存储该事件处理器的引用,来记录该方法,存储该方法的引用

完整事件例子(顾客点单):

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 OrderEventHandler OnOrder;*/
        //输入prop后双击tab键自动生成属性,并且通过双击tab去切换类型和变量名
        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; }
    }
}

b站up链接(讲得很好):

【事件•语法篇】如何声明自定义的事件以及事件的完整/简略声明格式(附:事件与委托的关系,事件与属性的相似之处)_哔哩哔哩_bilibili

  • 34
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity中,委托事件都是C#语言的特性。它们用于实现在不同对象之间进行通信和协作的机制。 委托是一种类型,它可以用来存储对方法的引用。我们可以将委托看作是一个变量,该变量引用一个或多个方法。当调用委托时,它将依次调用所有委托引用的方法。委托可以用于事件处理、回调等场景。 Unity中的事件是一种特殊的委托,它只能用于触发特定事件时通知其他对象。事件只能通过添加或移除处理程序来更改,而不能直接调用。事件通常用于游戏中的交互、动画等场景。 下面是一个使用委托事件的示例: ```csharp //声明一个委托 public delegate void MyDelegate(); public class MyClass { //声明一个事件 public event MyDelegate myEvent; //调用事件 public void CallEvent() { if(myEvent != null) { myEvent(); } } } public class OtherClass { public void MyMethod() { Debug.Log("MyMethod is called."); } } public class Test : MonoBehaviour { private MyClass myClass; private void Start() { myClass = new MyClass(); OtherClass otherClass = new OtherClass(); //将一个方法添加到事件的处理程序列表中 myClass.myEvent += otherClass.MyMethod; //调用事件 myClass.CallEvent(); //将方法从事件的处理程序列表中移除 myClass.myEvent -= otherClass.MyMethod; } } ``` 在这个示例中,我们声明了一个委托 `MyDelegate` 和一个类 `MyClass`,该类包含一个事件 `myEvent`。我们还声明了一个类 `OtherClass`,其中包含一个方法 `MyMethod`。在 `Test` 类的 `Start` 方法中,我们创建了一个 `MyClass` 的实例,并将 `OtherClass` 中的方法 `MyMethod` 添加到 `myEvent` 事件的处理程序列表中。然后,我们调用 `myEvent` 事件,此时 `MyMethod` 方法会被执行。最后,我们将 `MyMethod` 方法从 `myEvent` 事件的处理程序列表中移除。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值