【从零开始入门unity游戏开发之——C#篇33】C#委托(`Delegate`)和事件(`event` )、事件与委托的区别、Invoke()的解释
文章目录
- 一、委托(`Delegate`)
- 1、什么是委托?
- 2、委托的基本语法
- 3、定义自定义委托
- 4、如何使用自定义委托
- 5、多播委托
- 6、C# 中的系统委托
- 7、`GetInvocationList` 获取多个函数返回值
- 8、总结
- 二、事件(`event` )
- 1、事件是什么?
- 1.1. **事件是基于委托的存在**:
- 1.2. **事件是委托的安全包裹**:
- 1.3. **事件是一种特殊的变量类型**:
- 2、事件的使用
- 2.1 **声明事件语法**:
- 2.2 **事件的使用**:
- 2.3 **事件的触发**:
- 3、**委托与事件的区别**:
- 4、为什么要使用事件?
- 4.1. **防止外部随意置空委托**:
- 4.2. **防止外部随意调用委托**:
- 4.3. **事件封装了委托,增强了安全性**:
- 5、事件与委托的区别总结
- 5.1. **赋值操作**:
- 5.2. **调用方式**:
- 5.3. **临时变量**:
- 5.4. **可见性和封装**:
- 6、总结
- 三、补充`Invoke()`的解释说明
- 专栏推荐
- 完结
一、委托(Delegate
)
1、什么是委托?
委托(Delegate
)是 C# 中的一个重要概念,它可以被理解为“函数(方法)的容器
”。更具体地说,委托是用来存储、传递函数(方法)的类型,允许我们将方法作为参数传递给其他方法或存储在变量中。委托的本质是一个类,用来定义方法的类型(即方法的返回值和参数类型)。
- 委托本质上是一个类型,它定义了方法的类型:返回值类型和参数类型。
- 委托用于
存储方法
,它帮助你把方法传递给其他方法,或者在程序中动态调用。 - 委托是一个
引用类型
2、委托的基本语法
-
关键字:
delegate
-
语法:
[访问修饰符] delegate 返回值类型 委托名(参数列表);
示例:
delegate void MyDelegate(); // 无返回值,无参数的委托 delegate int MyDelegateWithParam(int x); // 返回 int,带一个参数的委托
- 委托可以声明在类(
class
)或命名空间(namespace
)中,一般情况下会声明在命名空间中。 - 委托声明的访问修饰符可以是
public
(默认是public
),也可以是private
,protected
,internal
等。
3、定义自定义委托
-
无参无返回值的委托
public delegate void MyFun();
-
带参数和返回值的委托
public delegate int MyFun2(int a);
-
泛型委托:
委托是支持泛型的,可以让返回值和参数的类型灵活变更。delegate T MyFun3<T, K>(T v, K k);
4、如何使用自定义委托
委托变量相当于一个函数的容器,可以存储函数,并通过委托来调用它。
-
基本用法
- 通过
new
关键字创建委托实例,并调用它。
public delegate void MyFun();static void Fun() {Console.WriteLine("Hello from Fun!"); }static void Main(string[] args) {MyFun f = new MyFun(Fun); // 创建委托实例f.Invoke(); // 调用委托,输出: Hello from Fun! }
- 通过
-
简化方式(委托隐式转换)
委托可以直接引用方法,而不需要new
操作符。MyFun f2 = Fun; f2(); // 调用 Fun() 方法
-
带参数的委托
如果委托方法需要参数,可以在调用时传递参数。public delegate int MyFun2(int a);static int Fun2(int x) {return x * 2; }static void Main(string[] args) {MyFun2 f3 = Fun2;Console.WriteLine(f3(3)); // 输出: 6 }
-
委托作为参数
委托经常作为方法的参数,用于传递行为。public delegate void MyFun(); // 定义委托类型class Test(){public void TestFun(MyFun fun){// 执行其他逻辑fun(); // 调用传入的委托方法}}class Program{static void FunLog(){Console.WriteLine("Hello from Fun!");}public static void Main(){MyFun Fun = FunLog; // 创建委托实例Test t = new Test();t.TestFun(Fun); // 传递委托}}
结果打印
5、多播委托
委托可以存储多个方法(多播委托)。通过多播委托,可以在一个委托实例中注册多个方法,调用时按顺序执行这些方法。
-
添加多个方法到委托
MyFun f = Fun; f += Fun2; f(); // 调用 Fun 和 Fun2 方法
-
移除方法
f -= Fun; f(); // 只调用 Fun2 方法
-
清空所有注册的方法
f = null;
6、C# 中的系统委托
C# 提供了多个预定义的委托类型,主要有 Action
和 Func
。这些委托可以大大简化我们的编程工作。
-
Action 委托
Action
委托用于没有返回值
的情况。它可以有0到16个参数。Action委托的语法结构
Action<参数类型1, 参数类型2, ..., 参数类型N>
-
无参数:
Action action = Fun; action += Fun2; action();// 调用 Fun 和 Fun2 方法
-
带参数:
Action<int, string> action2 = Fun2; action2(1, "Hello");
-
-
Func 委托
Func
委托用于有返回值
的情况。它也支持0到16个参数。
Func
委托的最后一个类型参数
总是方法的返回值类型
,而其他的参数类型则是方法的输入参数类型。Func 委托的语法结构
Func<参数类型1, 参数类型2, ..., 参数类型N, 返回值类型>
-
无参数,有返回值:
static int FunLog() {return 1; }public static void Main() {Func<int> func = FunLog;int result = func();Console.WriteLine(result);// 打印1 }
-
带参数,有返回值:
static int FunLog(string str){return int.Parse(str);}public static void Main(){Func<string, int> func = FunLog;int result = func("111");Console.WriteLine(result);// 打印111}
-
7、GetInvocationList
获取多个函数返回值
当用有返回值的委托容器存储多个函数时
using System;class Program
{static void Main(){// 创建一个 Func 委托,返回 string 类型Func<string> funTest = () =>{Console.WriteLine("第一个函数");return "1";};// 将更多函数添加到委托链funTest += () =>{Console.WriteLine("第二个函数");return "2";};funTest += () =>{Console.WriteLine("第三个函数");return "3";};// 如果直接调用委托,会执行所有函数逻辑,但是只能获取到最后一个返回值Console.WriteLine("直接调用返回值:");Console.WriteLine(funTest()); // 只会输出 "3"}
}
输出结果:
直接调用返回值:
第一个函数
第二个函数
第三个函数
3
如果想要获取每个函数的返回值,可以通过 GetInvocationList 方法
foreach (Func<string> del in funTest.GetInvocationList())
{Console.WriteLine(del()); // 输出每个函数的返回值
}
输出结果:
第一个函数
1
第二个函数
2
第三个函数
3
- funTest.GetInvocationList() 返回一个委托数组,数组中的每个元素都是一个 Func 委托实例,表示容器中每个方法。
- 我们通过 foreach 遍历这些委托实例,并单独执行每个方法,获取它们的返回值。
8、总结
- 委托 是 C# 中用来装载方法的类型,允许将方法作为参数传递或者存储。
- 委托的定义语法包括
delegate
关键字,后接返回类型、委托名称以及参数列表。 - 委托支持泛型,可以创建灵活的、可重用的函数容器。
- 委托可以作为方法的参数,或作为类的成员变量,用于实现事件、回调等功能。
- C# 提供了 没有返回值
Action
和 有返回值Func
等预定义的委托类型,可以减少开发者手动定义委托的工作量。
二、事件(event
)
在 C# 中,事件是基于委托的,它对委托进行了封装,提供了一些安全性和约束。事件是 C# 中一种特殊的成员,通常用于发布-订阅模式,允许一个对象(发布者)通知其他对象(订阅者)某些事情的发生。
1、事件是什么?
1.1. 事件是基于委托的存在:
- 委托可以理解为对方法的引用,而事件是基于委托的,委托在事件中扮演着核心角色。
1.2. 事件是委托的安全包裹:
- 事件通过封装委托,限制了委托的使用,使其更具安全性。特别是防止外部代码随意更改委托(赋值和调用)。
1.3. 事件是一种特殊的变量类型:
- 在 C# 中,事件是一种特殊的变量,它用于存储委托类型的引用,但它具有一些额外的限制。
2、事件的使用
2.1 声明事件语法:
事件的声明语法如下:
[访问修饰符] event 委托类型 事件名;
例如:
public event Action MyEvent;
2.2 事件的使用:
- 事件作为类的成员变量,类似于委托。
- 事件和委托的使用方法非常相似,主要的区别在于事件增加了访问限制。
示例:
public class Test
{public Action MyFun; // 委托类型成员变量public event Action MyEvent; // 事件类型成员变量
}
2.3 事件的触发:
- 事件的触发只能在事件的拥有者(类)内部进行,通过调用事件处理方法。
- 一般来说,可以通过一个方法来触发事件,例如:
public void DoEvent()
{if (MyEvent != null){MyEvent.Invoke(); // 安全地触发事件}
}
3、委托与事件的区别:
- 事件不能在类外部赋值:委托可以在类外部进行赋值(例如:
myFun = TestFun
),但事件只能通过+=
和-=
操作符来添加和移除事件处理器。 - 事件不能在类外部调用:委托可以在类外部直接调用(例如:
myFun()
),而事件只能在类内部触发(通常使用事件发布方法)。
示例:
public void TestFun()
{Console.WriteLine("123");
}// 委托使用
myFun += TestFun;
myFun(); // 调用委托// 事件使用
myEvent += TestFun; // 添加事件处理器
myEvent(); // 错误,无法在类外部调用事件
4、为什么要使用事件?
4.1. 防止外部随意置空委托:
- 事件的最大优势之一是它防止了外部代码直接修改委托的值(置空)。这保证了事件的安全性,防止委托被外部代码随意设置为
null
或重新赋值,避免了潜在的错误。
4.2. 防止外部随意调用委托:
- 事件提供了对委托的封装,确保事件只能通过
+=
或-=
操作符来添加和移除事件处理器,不能通过外部代码直接调用。
4.3. 事件封装了委托,增强了安全性:
- 事件是一种委托的封装,提供了更高的安全性和可控性,使得事件处理过程更加规范。外部代码无法直接修改事件的内容,只能通过规范的方式来订阅和退订事件。
5、事件与委托的区别总结
5.1. 赋值操作:
- 委托:可以在外部赋值(
=
)。 - 事件:不能直接赋值,只能通过
+=
和-=
操作符来添加和移除事件处理器。
5.2. 调用方式:
- 委托:可以在外部直接调用(
myFun()
)。 - 事件:只能在类内部通过触发方法调用(
MyEvent.Invoke()
)。
5.3. 临时变量:
- 委托:可以作为局部变量在方法中使用。
- 事件:不能作为临时变量,只能作为类的成员存在。
5.4. 可见性和封装:
- 委托:没有访问控制的封装机制,外部可以自由操作。
- 事件:有封装,提供了更严格的访问控制,确保安全性。
6、总结
- 事件本质上是一个委托的封装,提供了额外的安全性。它限制了委托在外部的赋值和调用,使得事件的触发只能在拥有事件的类内部进行。
- 事件的主要用途是实现发布-订阅模式,当某个对象发生变化时,能够通知其他对象,避免外部代码直接修改和调用事件。
- 事件与委托的主要区别在于访问权限的控制,事件通过严格的封装和访问控制来增强安全性,避免外部随意操作。
三、补充Invoke()
的解释说明
Invoke() 是委托
和事件
中常用的方法,它的作用是调用委托所指向的方法。当你在委托或者事件上调用 Invoke() 时,它会执行该委托或事件注册的所有方法。
示例:
public delegate void MyDelegate(string message);public class Program
{static void Main(){MyDelegate myDelegate = new MyDelegate(PrintMessage);myDelegate.Invoke("Hello, World!"); // 调用委托的 Invoke 方法}static void PrintMessage(string message){Console.WriteLine(message);}
}
Invoke()
是隐式调用的,因此你也可以直接使用 myDelegate()
来调用它。这两者的作用是相同的。实际上,Invoke()
是自动由 C# 编译器生成的,如果你使用 myDelegate()
,编译器会在幕后调用 Invoke()
。
对于事件同理,Invoke()
方法用于触发事件并调用所有注册的事件处理程序。事件本质上是封装了委托,因此通过 Invoke()
可以调用所有订阅该事件的方法。
专栏推荐
地址 |
---|
【从零开始入门unity游戏开发之——C#篇】 |
【从零开始入门unity游戏开发之——unity篇】 |
【制作100个Unity游戏】 |
【推荐100个unity插件】 |
【实现100个unity特效】 |
【unity框架开发】 |
完结
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
相关文章:

【从零开始入门unity游戏开发之——C#篇33】C#委托(`Delegate`)和事件(`event` )、事件与委托的区别、Invoke()的解释
文章目录 一、委托(Delegate)1、什么是委托?2、委托的基本语法3、定义自定义委托4、如何使用自定义委托5、多播委托6、C# 中的系统委托7、GetInvocationList 获取多个函数返回值8、总结 二、事件(event )1、事件是什么…...

大数据与机器学习(它们有何关系?)
想了解大数据和机器学习吗?我们将为你解释它们是什么、彼此之间有何关联,以及它们为何在数据密集型应用中如此重要。 大数据和机器学习是如何相互关联的? 大数据指的是传统存储方法无法处理的海量数据。机器学习则是计算机系统从观察结果和…...

深入浅出 Spring(一) | Spring简介、IOC理论推导、快速上手 Spring
1. spring 1.1 简介 Spring : 春天 —>给软件行业带来了春天 2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。 2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。 很难想象…...
IDEA 社区版 SpringBoot不能启动
报错原因,Failed to load class [javax.servlet.Filter] <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId><scope>provided</scope> </dependency>…...

职场常用Excel基础01-数据验证
大家好,excel在职场中使用非常频繁,今天和大家一起分享一下excel中数据验证相关的内容~ 在Excel中,数据验证(Data Validation)是一项非常有用的功能,它可以帮助用户限制输入到单元格中的数据类型和范围&am…...

活动预告 |【Part1】Microsoft Azure 在线技术公开课:数据基础知识
课程介绍 参加“Azure 在线技术公开课:数据基础知识”活动,了解有关云环境和数据服务中核心数据库概念的基础知识。通过本次免费的介绍性活动,你将提升在关系数据、非关系数据、大数据和分析方面的技能。 活动时间:01 月 07 日…...

RabbitMQ - 1 ( 7000 字 RabbitMQ 入门级教程 )
一: 在互联网行业,许多公司喜欢用动物命名产品或作为公司的 Logo 和吉祥物,比如腾讯的企鹅、京东的狗、美团的袋鼠、携程的海豚,而阿里更是凭借蚂蚁、飞猪、天猫、菜鸟、闲鱼、盒马等,打造了一座“动物园”。Rabbit&a…...

Docker Compose 构建 EMQX 集群 实现mqqt 和websocket
EMQX 集群化管理mqqt真香 目录 #目录 /usr/emqx 容器构建 vim docker-compose.yml version: 3services:emqx1:image: emqx:5.8.3container_name: emqx1environment:- "EMQX_NODE_NAMEemqxnode1.emqx.io"- "EMQX_CLUSTER__DISCOVERY_STRATEGYstatic"- …...
Spring 过滤器:OncePerRequestFilter 应用详解
在Web应用中,过滤器(Filter)是一个强大的工具,它可以在请求到达目标资源之前或响应返回客户端之前对请求或响应进行拦截和处理。然而,在某些情况下,我们可能希望确保过滤器逻辑在一次完整的HTTP请求中仅执行…...

3.CSS字体属性
3.1字体系列 CSS使用font-family属性定义文本的字体系列。 p{font-family:"微软雅黑"} div{font-family:Arial,"Microsoft Yahei",微软雅黑} 3.2字体大小 css使用font-size属性定义字体大小 p{ font-size:20px; } px(像素)大小是我们网页的最常用的单…...
微信小程序 单选多选radio/checkbox 纯代码分享
单选按钮 <radio-group class"radiogroup" bindchange"radioChange"> <label class"radio" wx:for"{{items}}"> <radio value"{{item.name}}" checked"{{item.checked}}" /> {{item.value}} &…...
k8s 部署meilisearch UI
https://github.com/riccox/meilisearch-ui 拉取镜像 sudo docker pull riccoxie/meilisearch-ui:latestk8s 部署 apiVersion: v1 kind: Service metadata:name: meilisearch-uinamespace: meilisearch spec:type: NodePortselector:app: meilisearch-uiports:- port: 24900…...

gitlab 还原合并请求
事情是这样的: 菜鸡从 test 分支切了个名为 pref-art 的分支出来,发布后一机灵,发现错了,于是在本地用 git branch -d pref-art 将该分支删掉了。之后切到了 prod 分支,再切出了一个相同名称的 pref-art 分支出来&…...

ChatGPT最新版本“o3”的概要
o3简介 o3于2024年12月20日发布——也就是OpenAI 12天直播的最后一天。目前处于安全性测试阶段。它是o1的继任者,旨在处理更复杂的推理任务。o3特别针对数学、科学和编程等领域进行了优化。 o3在多项基准测试中表现出色。例如,在ARC-AGI基准测试中&…...
uniapp——App下载文件,保存、打开文件(二)
uniapp如何下载文件、保存、打开文件 时光荏苒,2024即将过去! 迈向2025,祝大家新的一年工作顺利、万事如意,少一点BUG,涨一点工资…↖(ω)↗ 文章目录 uniapp如何下载文件、保存、打开文件下载文件保存并打开文件处理 …...

Postman接口测试05|实战项目笔记
目录 一、项目接口概况 二、单接口测试-登录接口:POST 1、正例 2、反例 ①姓名未注册 ②密码错误 ③姓名为空 ④多参 ⑤少参 ⑥无参 三、批量运行测试用例 四、生成测试报告 1、Postman界面生成 2、Newman命令行生成 五、token鉴权(“…...

【paddle】初次尝试
张量 张量是 paddlepaddle, torch, tensorflow 等 python 主流机器学习包中唯一通货变量,因此应当了解其基本的功能。 张量 paddle.Tensor 与 numpy.array 的转化 import paddle as paddle import matplotlib.pyplot as plt apaddle.to_t…...

01-2023年上半年软件设计师考试java真题解析
1.真题内容 在某系统中,类 Interval(间隔) 代表由下界(lower bound(边界))上界(upper bound )定义的区间。 要求采用不同的格式显示区间范围。 如[lower bound , upper bound ]、[ lower bound … upper bound ]、[ lower bou nd - upper bound &#x…...
一文讲清楚CSS3新特性
文章目录 一文讲清楚CSS3新特性1. 新增选择器特性2. 新增的样式3. 新增布局方式 一文讲清楚CSS3新特性 1. 新增选择器特性 层次选择器(div~p)选择前面有div的p元素伪类选择器 :first-of-type 表示⼀组同级元素中其类型的第⼀个元素:last-of-type 表示⼀组同级元素中其类型的最…...

系统设计案例:设计 Spotify
https://levelup.gitconnected.com/system-design-interview-question-design-spotify-4a8a79697dda 这是一道系统设计面试题,即设计 Spotify。在真正的面试中,你通常会关注应用程序的一两个主要功能,但在本文中,我想从高层次概述…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...

家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...

linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
适应性Java用于现代 API:REST、GraphQL 和事件驱动
在快速发展的软件开发领域,REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名,不断适应这些现代范式的需求。随着不断发展的生态系统,Java 在现代 API 方…...

在 Spring Boot 中使用 JSP
jsp? 好多年没用了。重新整一下 还费了点时间,记录一下。 项目结构: pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...

tauri项目,如何在rust端读取电脑环境变量
如果想在前端通过调用来获取环境变量的值,可以通过标准的依赖: std::env::var(name).ok() 想在前端通过调用来获取,可以写一个command函数: #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...