【C#学习笔记】委托和事件
文章目录
- 委托
- 委托的定义
- 委托实例化
- 委托的调用
- 多播委托
- 为什么使用委托?
- 官方委托
- 泛型方法和泛型委托
- 事件
- 为什么要有事件?
- 事件和委托的区别:
- 题外话——委托与观察者模式
委托
在 .NET 中委托提供后期绑定机制。 后期绑定意味着调用方在你所创建的算法中至少提供一个方法来实现算法的一部分。
说的更简单一点,我们可以通过调用委托实现一大串方法的处理。委托像是一个装函数的容器,在我之前的文章里,将其比作了服务员点菜时写的小单子,当触发委托的时候,就是将单子交给后厨,厨师就会按顺序做出我们点的菜。
委托的定义
当我们定义委托时,需要使用到delegate
关键字。
delegate void MyFun();// 委托不可重载delegate void MyFun<T>();// 带泛型的函数名MyFun<T>和MyFun不同delegate int MyFun2(int i);
委托是不可重载的。无论修改函数返回值定义还是增加参数,只要存在相同函数名的委托就无法通过编译。(但是带泛型的不是相同函数名,例如MyFun,MyFun,MyFun<T,K>可以同时存在)
委托实例化
当想要使用委托的时候,需要将其先实例化,存在两种实例化方式,new一个对象并为其赋值第一个函数:
int Input(int i){Debug.Log(i);return i;}void Start()
{MyFun2 myFun = new MyFun2(Input);// new的时候需要定义第一个调用函数MyFun myFun1 = null;// 委托初始化可为空,但是空委托触发时会报错
}
需要注意:第一,委托的初始化必须放在方法中执行,不能在定义时初始化;第二,委托的格式和函数的格式必须完全相同。委托的返回值指定什么类型,赋给委托的函数就必须也是相同类型,而委托有多少入参,函数也要有同样的入参:
delegate int MyFun2(int i);
int Input(int i)
{return i;
}
void Input1(int i)
{
}
int Input2(string i)
{return 1;
}
MyFun2 myFun = new MyFun2(Input);
myFun += Input1; // 返回值不同,报错
myFun = new MyFun2(Input2); // 入参不同,报错
委托的调用
当调用委托的时候,我们需要确保委托不为空,可以使用反射和直接调用的方法来调用委托:
myFun1();//委托为空执行会报错
myFun(100);
myFun.Invoke(100); //带有入参的委托需要在调用时给出入参
myFun1.Invoke(); //委托为空执行会报错
myFun1?.Invoke(); //使用?.Invoke(),当委托为空时不调用
多播委托
委托可以包含多个方法并触发,这样的委托被我们称为多播委托,对于委托中的方法,可以简单地使用如下语句进行增减:
myFun1 += Fun;
myFun1 -= Fun2;// 当减去方法的时候,若委托中没有对应函数,
//编译(即使委托为空)和执行都不会报错
myFun1 += SayHi;
在多播委托中,委托中事件的触发顺序是按照执行语句时我们向委托中添加的方法的顺序来执行的,如果先添加A再添加B方法,则就是先执行A再执行B。
为什么使用委托?
请看下面的一个例子:
class Test{public MyFun fun;public MyFun2 fun2;int i=10;public void TestFun(MyFun fun,MyFun2 fun2){i= i*100;fun2.Invoke(i);fun.Invoke();}}
在上面的这个类中,我们使用一个函数来接收两个委托,随后在函数中处理了参数i
并定义了两个委托的调用次序。而最终,这个类在实例化后可以在需要时来调度这个函数方法。
当我们需要在类在实例化后处理一系列方法,例如实例化了一个Monster
类的小怪A
,i
对应它的伤害,fun1,fun2对应攻击后会触发的一系列反应。这样一个小怪攻击的方法就简单的完成了。
一方面,我们想要调用函数的话,首先函数是无法作为参数传入到其他函数中的,其次如果调度函数将考虑一大堆问题:例如需要调度的函数的访问修饰是不是public,是否要先引入其他的类,如果要修改这个攻击方式怎么办等等问题。使用委托就不用考虑这些问题,只需要保证委托和调用方法的格式是相一致的,将委托丢入方法后直接调用,把整个问题抽象到只考虑我们应该在什么时候触发委托就行了。
另一方面,在我们的结构中,触发的是委托而不是函数,也就意味着我们可以随意地修改攻击方法触发的函数,只需简单地对委托进行方法的加减。
我想介绍的委托的另一个使用例子来自unity官方,在官方提供的New InputSystem中,每个按键触发的就是委托。这使得我们可以像接口一样很简单地修改按键对应的方法。以往是用if
检测按键触发然后调度方法,现在我们可以通过委托把该调度的方法直接加入到委托多播上。这样更方便修改,也更灵活。
官方委托
虽然我们可以自行定义委托,但是毕竟代码是给人看的,可能有人接受你的代码后不知道对应的类型是类,结构体还是什么东西,只有当他查看引用了之后才知道这是一个委托。而官方很贴心的提供了一些有名字的委托:
Action action = test.Fun; // void Action() 无参无返回委托
Action<string> action1 = NewString; // void Action<T>() 有参无返回泛型委托,最多接受16个泛型传入
action1 += Tstring; // 如果函数同样接收泛型,那么函数的泛型会自动接受Action委托声明时给出的对应泛型
Func<int> func = Input;// T Func<out T>(); 无参带返回值泛型委托,使用out修饰代表该委托是协变的,最后一个泛型决定返回值类型
Func<int,string,int> func1 = Input;// Result Func<T1,T2...Result>(T1 arg1, T2 arg2...) 有参带返回值泛型委托,最多接受16个泛型传入
官方委托总共有两个:Action
代表了无返回值委托,Func
则是带返回值委托。而这两个委托又有同名泛型定义,最多接受16个泛型,每个泛型对应着一个入参。
使用它们的时候就和正常委托一样,举个例子:
void Input(){}
int Input()
{return 1;
}
int Input<T1,T2>(T1 a,T2 b)
{return 1;
}
void Tstring<T>(T i){}
void NewString(string i){}Action action = Input; // Action无入参无返回值,对应delegate void Action()
Action<string> action1 = NewString;// Action有入参无返回值,对应void Action<T>(T t)
// 上句对应的NewString类型也要完全一致,也是无返回值,入参一个,类型为string
Func func = Input;// Func无入参有返回值,对应TResult Func<out TResult>(),out修饰协变
// 注意当使用Func委托的时候,至少需要定义一个泛型,这个泛型对应的不是入参而是返回值的类型
Func<int,string,int> func1 = Input;// 同理,右侧最后一个泛型int代表了返回值的类型
// Func定义的三个泛型,则需要委托的方法要有返回值,且有两个入参
上述是官方委托的使用方法,当不需要返回值的时候使用Aciton
,当需要返回值的时候使用Func
,并且还需要定义最后一个泛型来代表返回值的类型。
泛型方法和泛型委托
int Input<T1,T2>(T1 a,T2 b)
{return 1;
}
Func<int,string,int> func1 = Input;
在上述代码中,函数Input
定义了两个泛型,而委托Func
中有三个泛型,实际上他们是匹配的,毕竟Func
中最后一个泛型代表了返回值的类型。
现在有下列定义:
T Input<T1,T2,T>(T1 a,T2 b)
{T t = default(T);return t;
}
Func<int,string,int> func1 = Input; // 报错
上述代码看起来很合理,三个泛型依次赋值给Input的三个泛型,实际上不行。Func
只会把前两个泛型定义给函数,因此第三个泛型编译器无法推断。
泛型委托也接受元组,默认地,在调用时元组的第一项是.Item1
,第二项是.Item2
,依此类推。
Func<(int, int, int), (int, int, int)> doubleThem = ns => (2 * ns.Item1, 2 * ns.Item2, 2 * ns.Item3);
var numbers = (2, 3, 4);
var doubledNumbers = doubleThem(numbers);
当然也可以像这样自定义元组中每个项的名称:
Func<(int n1, int n2, int n3), (int, int, int)> doubleThem = ns => (2 * ns.n1, 2 * ns.n2, 2 * ns.n3);
事件
学习了委托,事件其实也就学习了,事件和委托基本上一模一样:
class Test
{delegate void NewDel();event NewDel MyFun; // 注意,定义事件时其访问性必须与委托一致
}
在定义事件时,使用event
关键字来修饰委托名并命名出事件委托的定义,事件和委托的使用基本一模一样,就不再赘述了。唯一的区别在于事件是无法在类的外部进行赋值和调用的:
class Test{public delegate void NewDel();public NewDel del = null;public event NewDel MyFun;public Test(){del = NewFun;MyFun = NewFun;}public void NewFun(){}}void Start(){Test t = new Test();t.del();t.del.Invoke();t.MyFun(); // 报错t.MyFun.Invoke(); // 报错t.MyFun = null; // 报错t.MyFun += Appli;t.MyFun -= Appli;}void Appli(){}
在类的外部,不能直接操作Event
,只能进行加减函数的操作。并且事件也无法作为临时变量在函数中使用,智能作为成员存在于类和接口以及结构体中。
为什么要有事件?
- 防止外部随意置空委托
- 防止外部随意调用委托
- 事件相当于对委托进行了一次封装,使其更加安全
事件和委托的区别:
- 事件不能在外部赋值,在外部只能对其进行函数委托的加减操作
- 事件不能在外部执行,而委托在哪都能执行
- 事件不能作为函数中的临时变量,而委托可以
题外话——委托与观察者模式
首先简单介绍一下什么是观察者模式,观察者模式是对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
举个例子,现在有n个外贸公司,这些公司都有进口和出口的业务,当人民币贬值时,人民币贬值导致这些公司更倾向出口业务,而人民币升值则更倾向进口业务:
class Company{public void Update(bool 贬值了){if (贬值了) { Debug.Log("出口"); }else { Debug.Log("进口"); }}}class CHY{private List<Company> Companies = new List<Company>();bool state = false;void 贬值()// 原谅我不懂贬值的英文,幸好C#可以起中文名,作为举例足够了{state = true;Debug.Log("人民币贬值");}void 升值(){state = false;Debug.Log("人民币升值");}bool setState(){// 判断升值还是贬值的代码return state;}void getState(){foreach(var company in Companies){company.Update(state);}}}
在上述代码中,如果人民币发生增值和贬值,都会通过getState()
来通知那些观察者(公司),当观察者发现人民币汇率变化,则选择相应的战略计划。这就是一个简单的观察者模式。
在观察者模式中,明显这些相关联的类具有很强的依赖性,被观察者发生变化则会向所有观察者广播通知,而观察者则会做出相应的反应。
那么委托和观察者模式有什么关系呢?仔细想来,其实委托和观察者本质上是相似的,他们的处理模式都是:
启动——通知——逐一处理
现在让我们把上述观察者事件加入到一个委托当中,也就是:
Func<bool> ChangeState = setState;// 代码有点小问题,意思到了就行
ChangeState += Company1.Update;
ChangeState += Company2.Update;
......
ChangeState.Invoke(state);
当我们触发ChangeState
事件之后,人民币状态改变了,而委托中附加的公司也通知了。所实现的功能和观察者模式是一样的。那我们为什么要用委托实现呢?原因是解耦。
第一个例子中观察者和被观察者的关系是十分紧密的,以致于存在依赖,需要由被观察者来通知观察者。耦合性过高。而使用委托之后就无需定义被观察者中的getState()
方法,也无需定义List<Company>
,只需将观察者的更新状态方法添加到委托中即可。大大降低了两个类的耦合性。使用委托,被观察者完全不知道观察者的存在,这才是真正的观察者模式。
相关文章:

【C#学习笔记】委托和事件
文章目录 委托委托的定义委托实例化委托的调用多播委托 为什么使用委托?官方委托泛型方法和泛型委托 事件为什么要有事件?事件和委托的区别: 题外话——委托与观察者模式 委托 在 .NET 中委托提供后期绑定机制。 后期绑定意味着调用方在你所…...

堆排序简介
概念: 堆排序是一种基于二叉堆数据结构的排序算法。它的概念是通过将待排序的元素构建成一个二叉堆,然后通过不断地取出堆顶元素并重新调整堆的结构来实现排序。 算法步骤: 构建最大堆(或最小堆):将待排…...

React Diff算法
文章目录 React Diff算法一、它的作用是什么?二、React的Diff算法1.了解一下什么是调和?2.react的diff算法3.React Diff的三大策略4.tree diff:1、如果DOM节点出现了跨层级操作,Diff会怎么办? 5. component diff:6. e…...

07 mysql5.6.x docker 启动, 无 config 目录导致客户端连接认证需要 10s
前言 呵呵 最近再一次 环境部署的过程中碰到了这样的一个问题 我基于 docker 启动了一个 mysql 服务, 然后 挂载出了 数据目录 和 配置目录, 没有手动复制配置目录出来, 所以配置目录是空的 然后 我基于 docker 启动了一个 nacos, 配置数据库设置为上面的这个 mysql 然后 启…...

GO GC
GO GC 垃圾回收(Garbage Collection,简称GC)是编程语言中提供的自动的内存管理机制,自动释放不需要的对象,让出存储器资源,无需程序员手动执行。 Golang中的垃圾回收主要应用三色标记法,GC过程和其他用户goroutine可…...

ECharts配合Node.js爬虫实现数据可视化
数据可视化简介 可视化技术是将数据和信息以图形化的方式展示出来,以便更好地理解和分析。可视化技术通常使用各种图表、图形、动画和交互式效果来呈现数据。可视化技术有以下几个基本概念: 数据:可视化技术的基础是数据。数据可以是数字、文…...

[Linux] C获取键盘,鼠标数据
键盘检测指令:cat /dev/input/event1 | hexdump 鼠标检测指令:cat /dev/input/event2 | hexdump 当键盘/鼠标有输入时,会有对应的一堆16进制输出。它其实对应着input_event结构体【24字节】。 struct input_event {struct timeval time;_…...

户外跑步用什么耳机、户外运动耳机推荐
跑步是一项简单的运动,只需要交替迈左右腿就可以进行。然而,跑步有时可能变得单调乏味。即使是意志坚定、热爱跑步的人,在这个漫长的过程中也会感到乏味,更不用说像你我这样的普通跑者了。音乐能够让跑步变得更加有趣,…...

ubuntu设置系统代理
安装trojan等代理工具并配置启动,得到端口号 例如 10.10.1.10:8080系统代理设置 我们将在/etc/profile.d/proxy.sh下添加一个shell脚本文件,这将确保设置适用于所有已登录的用户: sudo vim /etc/profile.d/proxy.sh将以下内容写到文档中&…...

java定时任务如何取消
java定时任务如何取消,并比如,我之前想每周二晚上6点自动生成一条devops流水线,现在我想停掉 答案: 在Java中,可以使用ScheduledExecutorService类来创建定时任务。要取消定时任务,可以调用ScheduledFutur…...

gitlab 9.05 版本获取合并请求的API接口报错404是为什么
gitlab 9.05 版本获取合并请求的API接口报错404是为什么 答案: 出现404错误表示请求的资源未找到。在这种情况下,可能有以下几个原因导致API接口报错404: 版本不匹配:请确保你使用的是GitLab 9.05版本的API接口,如果使…...

微服务(多级缓存)
目录 多级缓存 1.什么是多级缓存 2.JVM进程缓存 2.2.初识Caffeine 2.3.实现JVM进程缓存 2.3.1.需求 2.3.2.实现 3.Lua语法入门 3.1.初识Lua 3.1.HelloWorld 3.2.变量和循环 3.2.1.Lua的数据类型 3.2.2.声明变量 3.2.3.循环 3.3.条件控制、函数 3.3.1.函数 3.3.…...

阿里云配置MySQL-server 8.0远程登录
Ubuntu 22.04 LTS 安装MySQL-Server 8.0 # apt search mysql-server # apt install mysql-server重建服务 # service mysql stop # vi /etc/mysql/mysql.conf.d/mysqld.cnf ... bind-address 0.0.0.0 ... # service mysql start # lsof -i:3306 COMMAND PID USER FD …...

清洁能源使用的社会发展意义
应用清洁能源是转变经济增加途径的有效手段,能够在减少污染物、降低企业经营成本的同时,提高企业经济效益和社会经济效益。 应用清洁能源是保护环境的最佳方式和必然选择,改变末端治理的现状,采取以预防为主的环境保护与发展理…...

针对论坛系统进行功能测试和性能测试
项目链接:飞鸽论坛 目录 一. 项目背景 二. 项目功能 三. 功能测试 注册: 登录: 更改用户信息: 发布帖子: 更新帖子信息: 点赞: 评论: 发送私信: 测试报告 四. 性能测试 Virtual User Generator Controller Analysis 测试报告: 一. 项目背景 该论坛系统采用前…...

Android App的设计规范
Android App 设计规范是为开发者和设计师提供的一系列准则和建议,以确保应用在 Android 设备上的外观、交互和用户体验保持一致。以下是一些常见的 Android App 设计规范要点,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软件外包开…...

paddleclas ImportError: cannot import name ‘Identity‘ from ‘paddle.nn‘
使用paddlepaddle的 paddleclas 官方demos时 ,报错如图 ImportError: cannot import name ‘Identity’ from ‘paddle.nn’ 解决方案很简单: 找到调用 Identity 的位置: 注释掉就解决啦 !!! 搞定!!!…...

Debezium系列之:深入理解Debezium Server Operator和实际应用Debezium Server Operator案例详解
Debezium系列之:深入理解Debezium Server Operator和实际应用Debezium Server Operator案例详解 一、认识Debezium Server Operator二、深入理解Debezium Server和Debezium Server实际应用案例详解三、Debezium Server Operator安装步骤四、Debezium Operator使用案例五、post…...

ansible批量创建crontab文件并添加到定时任务
Ansible 来修改 crontab 文件并添加计划任务。用于将你提供的 cron 行添加到特定用户的 crontab 中: --- - name: Add cron job to users crontabhosts: your_target_hosttasks:- name: Add cron jobcron:name: "ntpdate_job"minute: "0"hour:…...

什么是 API ?
一、API 的定义:数据共享模式定义 4 大种类 作为互联网从业人员,API 这个词我耳朵都听起茧子了,那么 API 究竟是什么呢? API 即应用程序接口(API:Application Program Interface),…...

CSS实现内凹圆角,从而实现圆角边框
1、代码 <!DOCTYPE html> <html><head><style>.uu {position: relative;width: 400px;height: 300px;}img {width: 100%;height: 100%;z-index: 1;}.box_right_top {background-image: radial-gradient(circle at left bottom, transparent 50px, whi…...

Spring中的自定义注解
在Spring中,注解是一种非常使用的工具。 因其强大的功能,极大的提高了我们开发效率。 但是当遇到一些特殊业务时,框架自有的注解已经不能满足我们的需求了,这时我们就可以添加自定义注解来满足我们的业务需求。 我们用interfac…...

前端需要理解的设计模式知识
设计模式的原则:1. 单一职责原则(一个对象或方法只做一件事) 2. 最少知识原则(尽可能少的实体或对象间互相作用) 3. 开放封闭原则(软件实体具有可扩展且不可修改) 设计模式是通过代码设计经验总…...

1、攻防世界第一天
1、网站目录下会有一个robots.txt文件,规定爬虫可以/不可以爬取的网站。 2、URL编码细则:URL栏中字符若出现非ASCII字符,则对其进行URL编码,浏览器将该请求发给服务端;服务端会可能会先对收到的url进行解码࿰…...

分布式事务(7):SpringCloud2.0整合LCN
目前LCN版本已经升级为4.0了,但是官方没有SpringCloud2.0的demo案例。 因为LCN本身是开源的,有些大神对LCN框架源码做修改,可以支持SpringCloud2.0版本。 下载地址:https://download.csdn.net/download/u013938578/88251904 1 下载LCN服务端源码 https://download.csdn.…...

机器学习实战14-在日本福岛核电站排放污水的背景下,核电站对人口影响的分析实践
大家好,我是微学AI,今天给大家介绍一下机器学习实战14-在日本福岛核电站排放污水的背景下,核电站对人口影响的分析实践。 近日,日本政府举行内阁成员会议,决定于2023年8月24日启动福岛核污染水排海。当地时间2023年8月24日13时&am…...

4G智慧电力物联网:建设高效智能,引领电力行业革新!
随着4G与物联网技术的快速发展为电力行业提供了更高效、可靠、智能化的解决方案。本文中智联物联将为大家分享智慧电力系统中的一些关键的物联网技术和通讯设备,如工业4G路由器、分布式发电站、数据采集传输、远程监控管理以及变电站监测。 光伏发电站是电力行业中重…...

安防视频监控平台EasyCVR视频集中存储平台接入RTSP设备出现离线情况的问题解决方案
安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快,可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等,以及支持厂家私有协议与SDK接入,包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…...

达梦数据库分区表介绍
概述 本文将对达梦数据库分区表概念、创建、维护进行介绍。 1.分区表概念 1.1 分区表使用场景 近几年,随着移动支付快速发展,银行交易系统中【移动小微支付场景】使用越来越多,系统中流水账单表数据量巨大,往往上TB。 为了提高…...

Python爬虫库之urllib使用详解
一、Python urllib库 Python urllib 库用于操作网页 URL,并对网页的内容进行抓取处理。 Python3 的 urllib。 urllib 包 包含以下几个模块: urllib.request - 打开和读取 URL。 urllib.error - 包含 urllib.request 抛出的异常。 urllib.parse - 解…...