首页 > 编程语言 >【Unity&&C#】委托与事件笔记

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

时间:2024-06-23 16:27:21浏览次数:3  
标签:委托 C# OrderEventArgs Unity 事件 && 类型 public 处理器

委托

委托的定义

委托是个类,分为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; }
    }
}

标签:委托,C#,OrderEventArgs,Unity,事件,&&,类型,public,处理器
From: https://blog.csdn.net/Hotgun2222/article/details/139901041

相关文章

  • 后docker时代的docker安装方法.md
    feodra亲测可用#!/bin/shset-e#DockerEngineforLinuxinstallationscript.##Thisscriptisintendedasaconvenientwaytoconfiguredocker'spackage#repositoriesandtoinstallDockerEngine,Thisscriptisnotrecommended#forproductionenv......
  • JUC锁: 锁核心类AQS详解
    AbstractQueuedSynchronizer抽象类是核心,需要重点掌握。它提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架。@立刀旁目录#带着BAT大厂的面试问题去理解#AbstractQueuedSynchronizer简介#AQS核心思想#AQS对资源的共享方式#AQS底层使用了模......
  • 【漏洞复现】WordPress MasterStudy LMS插件 SQL注入漏洞(CVE-2024-1512)
    0x01产品简介WordPress和WordPressplugin都是WordPress基金会的产品。WordPress是一套使用PHP语言开发的博客平台。该平台支持在PHP和MySQL的服务器上架设个人博客网站。WordPressplugin是一个应用插件。0x02漏洞概述WordPressPluginMasterStudyLMS3.2.5版本......
  • unity麦扣x唐老狮3DRPG笔记分享,ProBuilder插件基本介绍(持续更新)
    声明:本文仅用于个人笔记及学习交流,禁止用作任何商业用途唐老师没有讲过这些插件,所以现在还没轮结合到唐老狮的课程的阶段在具体写代码以及介绍unity本体功能的时候唐老师的课程知识点会融入进来另外该插件功能过多,而用的较少所以很多功能就只做介绍,知道大概即可  首......
  • AG32 MCU Start Kit 开发板快速入门及 21天体验活动
    AG32IDE开发环境搭建-完整版海振远科技2024-6-18AG32MCU开发板的使用使用准备在使用开发板前,请确认已经安装好开发环境。安装环境过程,请参考文档《AG32开发环境搭建.pdf》上电:给开发板5V供电,打开开关,可以看到电源旁边的小红灯亮起。使用example例程打开ex......
  • 目标检测0:layman学习Faster-RCNN算法(基于VOC数据进行训练)
    分享:Bubbliiiing的学习小课堂博主的专栏《睿智的目标检测》中对Faster-RCNN有较为详细的描述。CSDN 链接:睿智的目标检测27——Pytorch搭建FasterR-CNN目标检测平台源代码下载  :https://github.com/bubbliiiing/faster-rcnn-pytorchB站讲解链接:配置Tensorflow+Keras......
  • ChatGPT原理和训练【 ChatGPT是由OpenAI开发】
    本人详解作者:王文峰,参加过CSDN2020年度博客之星,《Java王大师王天师》公众号:JAVA开发王大师,专注于天道酬勤的Java开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯山峯转载说明:务必注明来源(注明:作者:王文峰哦)ChatGPT原理和......
  • 对于spring cloud的了解到入门
    一、SpringCloud介绍1.概念:Springcloud是一系列框架的有序集合。它利用springboot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用springboot的开发风格做到一键启动和部署。2.优缺点(主......
  • Centos7.5删除virbr0网卡
    1.ifconfig查看linux网卡ip[root@localhost~]#ifconfigens33:flags=4163<UP,BROADCAST,RUNNING,MULTICAST>mtu1500inet192.168.44.128netmask255.255.255.0broadcast192.168.44.255inet6fe80::27ec:9128:8fca:ee44prefixlen64scopeid0x20<link>ether0......
  • vscode开发纯java项目兼容eclipse
    最近想使用vscode作为开发工具逐步替代eclipse,但是不影响eclipse作为项目管理的配置。以下是踩坑过程:1、项目之间的依赖。如主projectA依赖projectB,projectB并不是已jar包的形式,而是项目的形式在eclipse中的,eclipse有个很方便的功能是直接把项目添加进依赖中,vscode貌似找不到直接......