C#8.0本质论第十四章--事件

C#8.0本质论第十四章–事件

委托本身是一个更大的模式(Pattern)的基本单位,称为Publish-Subscribe(发布-订阅)或Observer(观察者)。

14.1使用多播委托实现Publish-Subscribe模式

14.1.1定义订阅者方法
public class Cooler
{public Cooler(float temperature){Temperature = temperature;}// Cooler is activated when ambient temperature// is higher than thispublic float Temperature { get; set; }// Notifies that the temperature changed on this instancepublic void OnTemperatureChanged(float newTemperature){if(newTemperature > Temperature){System.Console.WriteLine("Cooler: On");}else{System.Console.WriteLine("Cooler: Off");}}
}public class Heater
{public Heater(float temperature){Temperature = temperature;}// Heater is activated when ambient temperature// is lower than thispublic float Temperature { get; set; }// Notifies that the temperature changed on this instancepublic void OnTemperatureChanged(float newTemperature){if(newTemperature < Temperature){System.Console.WriteLine("Heater: On");}else{System.Console.WriteLine("Heater: Off");}}
}
14.1.2定义发布者
public class Thermostat
{// Define the event publisher (initially without the sender)public Action<float>? OnTemperatureChange { get; set; }public float CurrentTemperature { get; set; }
}
14.1.3连接发布者和订阅者
public class Program
{public static void Main(){Thermostat thermostat = new();Heater heater = new(60);Cooler cooler = new(80);thermostat.OnTemperatureChange +=heater.OnTemperatureChanged;thermostat.OnTemperatureChange +=cooler.OnTemperatureChanged;Console.Write("Enter temperature: ");string? temperature = Console.ReadLine();if (!int.TryParse(temperature, out int currentTemperature)){Console.WriteLine($"'{temperature}' is not a valid integer.");return;}thermostat.CurrentTemperature = currentTemperature;}
}
14.1.4调用委托
public class Thermostat
{// ...public float CurrentTemperature{get { return _CurrentTemperature; }set{if (value != CurrentTemperature){_CurrentTemperature = value;// Call subscribers// Incomplete, check for null needed// ...OnTemperatureChange(value);// ...}}}private float _CurrentTemperature;
}
14.1.5检查空值
public class Thermostat
{// Define the event publisherpublic Action<float>? OnTemperatureChange { get; set; }public float CurrentTemperature{get { return _CurrentTemperature; }set{if(value != CurrentTemperature){_CurrentTemperature = value;// If there are any subscribers,// notify them of changes in // temperature by invoking said subscribersOnTemperatureChange?.Invoke(value);     // C# 6.0}}}private float _CurrentTemperature;
}

注意:OnTemperatureChange?.Invoke(value);

空条件操作符的优点在于,它采用特殊逻辑防范在执行空检查后订阅者调用一个过时处理程序(空检查后有变)导致委托再度为空

在C#6.0之前不存在这种特殊的、不会被干扰的空检查逻辑。老版本实现稍微麻烦一点。

    public float CurrentTemperature{get { return _CurrentTemperature; }set{if(value != CurrentTemperature){_CurrentTemperature = value;Action<float>? localOnChange=OnTemperatureChange;if(localOnChange!=null){localOnChange(value);}}}}

不是一上来就检查空值,而是先将OnTemperatureChange赋给第二个委托局部变量localOnChange。这个简单的修改可确保在检查空值和发送通知之间,如一个不同的线程移除了所有OnTemperatureChange订阅者,将不会引发NullReferenceException异常。

既然委托是引用类型,肯定有人会感到疑惑:为什么赋值给一个局部变量,再用那个局部变量就能保证null检查的线程安全性?因为localOnChange指向的位置就是OnTemperatureChange指向的位置,所以很自然的结论是:OnTemperatureChange中发生的任何变化都将在localOnChange中反映。

但实情并非如此。事实上,对OnTemperatureChange-=< subscriber >的任何调用都不会从OnTemperatureChange删除一个委托,而使它包含的委托比之前少一个。相反,该调用会赋值一个全新的多播委托,原始委托不受任何影响。

虽然这样可以防范调用空委托,但不能防范所有可能的竞态条件。例如一个线程拷贝委托,另一个将委托重置为null,然后原始线程调用委托之前的值,向一个已经不在列表中的订阅者发生通知。

14.1.6委托操作符

使用赋值操作符会清除之前的所有订阅者,并允许用新订阅者替换。这是委托很容易让人犯错的地方,因为在本来应该使用“+=”操作符的时候,很容易会错误地写成“=”。

无论“+”“-”还是它们的复合版本,内部都使用静态方法System.Delegate.Combine()和System.Delegate.Remove()来分别实现。

14.1.7顺序调用

MulticastDelegate类事实上维护者一个Delegate对象链表。调用多播委托时,链表中的委托实例被顺序调用。通常,委托按它们添加的顺序调用,但CLI并未对此做出规定,而且顺序可能被覆盖所以程序员不应依赖特定调用顺序

14.1.8错误处理

一个订阅者抛出异常,链中的后续订阅者就收不到通知。

为避免该问题,必须手动遍历订阅者列表,并单独调用它们。可以从委托的GetInvocationList()方法获取一份订阅者列表。

14.1.9方法返回值和传引用

还有一种情况需要遍历委托调用列表而非直接调用一个委托。这种情况的委托要么不返回void,要么具有ref或out参数。

14.2理解事件

14.2.1事件的作用

使用事件的好处是,只有直接持有一个事件对象的类可以调用这个事件对象,其他的类只能使用+=或-=向这个事件对象添加或删除对事件的订阅。(我试了下,自己类是可以用=覆盖的)

event关键字的作用就是提供额外的封装。

所以事件与委托的区别就是:1.只有直接持有一个事件对象的类可以调用这个事件对象。2.其他的类只能使用+=或-=向这个事件对象添加或删除对事件的订阅。

14.2.2声明事件

C#用event关键字声明事件,虽然看起来像是一个字段修饰符,但event定义了新的成员类型。

    public class TemperatureArgs : System.EventArgs{public TemperatureArgs(float newTemperature){NewTemperature = newTemperature;}public float NewTemperature { get; set; }}// Define the event publisherpublic event EventHandler<TemperatureArgs> OnTemperatureChange = delegate { };

普通委托另一个潜在缺陷在于很容易忘记在调用委托之前检查null值。幸好,在声明事件时可以赋值一个空白委托delegate { },就可引发事件而不必检查是否有任何订阅者。

14.2.3编码规范
14.2.4泛型和委托

事件的内部机制:C#编译器获取带有event关键字修饰符的public委托变量,在内部将委托声明为private,并添加了两个方法和两个特殊的事件块。简单地说,event关键字是编译器生成适合封装逻辑的C#快捷方式。

public class Thermostat
// ...public event EventHandler<TemperatureArgs>? OnTemperatureChange;
}

C#编译器遇到event关键字后生成的CIL代码等价于下面代码

public class Thermostat
// ...// Declaring the delegate field to save the // list of subscribersprivate EventHandler<TemperatureArgs>? _OnTemperatureChange;public void add_OnTemperatureChange(EventHandler<TemperatureArgs> handler){System.Delegate.Combine(_OnTemperatureChange, handler);}public void remove_OnTemperatureChange(EventHandler<TemperatureArgs> handler){System.Delegate.Remove(_OnTemperatureChange, handler);}#if ConceptualEquivalentCodepublic event EventHandler<TemperatureArgs> OnTemperatureChange{//Would cause a compiler erroradd{add_OnTemperatureChange(value);}//Would cause a compiler errorremove{remove_OnTemperatureChange(value);}} 
14.2.5实现自定义事件

C#允许添加自定义的add和remove块。

public class Thermostat
{public class TemperatureArgs : System.EventArgs// ...// Define the event publisherpublic event EventHandler<TemperatureArgs> OnTemperatureChange{add{_OnTemperatureChange = (EventHandler<TemperatureArgs>)System.Delegate.Combine(value, _OnTemperatureChange);}remove{_OnTemperatureChange = (EventHandler<TemperatureArgs>?)System.Delegate.Remove(_OnTemperatureChange, value);}}protected EventHandler<TemperatureArgs>? _OnTemperatureChange;public float CurrentTemperature// ...private float _CurrentTemperature;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/149618.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java命令 jmap 堆参数分析

jmap -heap pid 展示pid的整体堆信息 bash-4.4# jmap -heap 10 Attaching to process ID 10, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.172-b11using thread-local object allocation. Garbage-First (G1) GC with 8 th…

给EmEditor添加自定义外部工具DuilibPreviewer

duilib是一款xml描述UI布局的优秀的c开源界面库&#xff0c;为了方便开发布局UI&#xff0c;有网友制作了预览工具DuilibPreviewer&#xff0c;源码链接https://github.com/juhuaguai/duilib/tree/master/DuilibPreview。 为了进一步方便开发&#xff0c;便于随时预览自己用xm…

kettle创建数据库资源库kettle repository manager

数据库资源库是将作业和转换相关的信息存储在数据库中&#xff0c;执行的时候直接去数据库读取信息&#xff0c;很容易跨平台使用。 创建数据库资源库&#xff0c;如图 1.点击Connect 2.点击Repository Manager 3.点击Other Repository 4.点击Database Repository 在选择Ot…

AI监管规则:各国为科技监管开辟了不同的道路

AI监管规则&#xff1a;各国为科技监管开辟了不同的道路 一份关于中国、欧盟和美国如何控制AI的指南。 编译 李升伟 茅 矛 &#xff08;特趣生物科技有限公司&#xff0c;广东深圳&#xff09; 插图&#xff1a;《自然》尼克斯宾塞 今年5月&#xff0c;科技公司OpenAI首席…

深兰科技成功入选《2023年度国家知识产权优势企业名单》

2023年11月13日&#xff0c;国家知识产权局正式公布了《2023年度国家知识产权优势企业的名单》(以下简称“《名单》”)。深兰人工智能科技(上海)股份有限公司成功入选&#xff0c;荣获“国家知识产权优势企业”称号。 “国家知识产权优势企业”是指企业经营范围属于国家重点发展…

CUDA安装

在cmd中输入nvidia-smi。显示CUDA Version&#xff1a;12.3&#xff0c;所以只能下载小于等于12.3的版本。如下图&#xff1a; 进这个网址&#xff1a;https://developer.nvidia.com/cuda-toolkit-archive 选择一个版本下载。 选择完后之后这样选择&#xff1a; 最后点击下载即…

综述:目标检测二十年(机翻版)(未完

原文地址 20年来的目标检测&#xff1a;一项调查 摘要关键词一 介绍二 目标检测二十年A.一个目标检测的路线图1)里程碑&#xff1a;传统探测器Viola Jones探测器HOG检测器基于可变形零件的模型&#xff08;DPM&#xff09; 2)里程碑&#xff1a;基于CNN的两阶段探测器RCNNSPPN…

axios升级依赖版本后报错SyntaxError: Cannot use import statement outside a module

Axios构建为ES模块&#xff0c;而不是在Node中运行时的CommonJs。Jest的问题是它在Node中运行代码。这就是为什么告诉Jest转换Axios有效的原因。 Axios SDK附带了一个用于Node env的内置CommonJs文件。因此&#xff0c;我们可以通过将以下内容添加到您的package.json来修复它&a…

Ubuntu20.04 安装微信 【wine方式安装】推荐

安装步骤: 第一步:安装 WineHQ 安装包 先安装wine,根据官网指导安装即可。下载 - WineHQ Wikihttps://wiki.winehq.org/Download_zhcn 如果您之前安装过来自其他仓库的 Wine 安装包,请在尝试安装 WineHQ 安装包之前删除它及依赖它的所有安装包(如:wine-mono、wine-gec…

在PostGIS中进行点数据的栅格化

说明 介绍在PotGIS中将点数据转换为栅格数据。 关键字: raster、point、PostGIS 环境准备 Postgresql版本:PostgreSQL 14.0, 64-bitPostGIS版本:POSTGIS="3.3.2"QGIS版本:3.28.3-Firenze基本步骤 一、数据准备 测试数据中有一张点数据表,坐标系3857。 CRE…

.NET6使用MiniExcel根据数据源横向导出头部标题及数据

.NET6MiniExcel根据数据源横向导出头部标题 MiniExcel简单、高效避免OOM的.NET处理Excel查、写、填充数据工具。 特点: 低内存耗用&#xff0c;避免OOM、频繁 Full GC 情况 支持即时操作每行数据 兼具搭配 LINQ 延迟查询特性&#xff0c;能办到低消耗、快速分页等复杂查询 轻量…

vue.js 短连接 动态连接

有这么一种场景&#xff0c;我们实现了某个业务&#xff0c;现在需要将这个业务连接对外推广以期实现我们的运营、推广、佣金目的&#xff0c;那么我们如何实现呢&#xff1f; 比如这个页面连接为&#xff1a; https://mp.domain.com/user/creation/editor?spm1&userno12…

“一键搜索,海量商品任你选!多平台聚合,购物更便捷!“

对于多平台聚合搜索&#xff0c;根据关键词取商品列表&#xff0c;您需要使用第三方服务或软件来实现。以下是一些可能的选择&#xff1a; 使用第三方聚合搜索工具&#xff1a;有些第三方工具可以聚合多个电商平台的商品数据&#xff0c;并提供统一的搜索接口。您可以使用这些…

基于数据库(MySQL)与缓存(Redis)实现分布式锁

分布式锁 分布式锁&#xff1a;分布式锁是在分布式的情况下实现互斥类型的一种锁 实现分布式锁需要满足的五个条件 可见性&#xff1a;多个进程都能看到结果互斥性&#xff1a;只允许一个持有锁的对象的进入临界资源可用性&#xff1a;无论何时都要保证锁服务的可用性&#x…

查看,取消终端代理

查看 echo $http_proxy echo $https_proxy在 Linux 终端中&#xff0c;你可以使用以下命令取消 HTTP 和 HTTPS 的代理设置&#xff1a; export http_proxy"" export https_proxy""或者使用 unset 命令取消这两个代理设置&#xff1a; unset http_prox…

T10 数据增强

文章目录 一、准备环境和数据1.环境2. 数据 二、数据增强&#xff08;增加数据集中样本的多样性&#xff09;三、将增强后的数据添加到模型中四、开始训练五、自定义增强函数六、一些增强函数 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f…

docker数据卷详细讲解及数据卷常用命令

docker数据卷详细讲解及数据卷常用命令 Docker 数据卷是一种将宿主机的目录或文件直接映射到容器中的特殊目录&#xff0c;用于实现数据的持久化和共享。Docker 数据卷有以下特点&#xff1a; 数据卷可以在一个或多个容器之间共享和重用&#xff0c;不受容器的生命周期影响。…

CSS-表格独有属性

属性名&#xff1a;able-layout功能&#xff1a;设置列宽度属性值&#xff1a; auto&#xff08;默认值&#xff09;&#xff1a;自动&#xff0c;列宽根据内容计算 table-layout: auto; fixed&#xff1a;固定列宽&#xff0c;平均分 table-layout: fixed; 属性名&#xff1a;…

C语言中文网 - Shell脚本 - 8

第1章 Shell基础&#xff08;开胃菜&#xff09; 8. Linux Shell命令提示符 启动 Linux 桌面环境自带的终端模拟包&#xff0c;或者从 Linux 控制台登录后&#xff0c;便可以看到 Shell 命令提示符。看见命令提示符就意味着可以输入命令了。命令提示符不是命令的一部分&#x…

Linux QT交叉编译环境安装

参考链接 linux交叉编译Qt_linux qt 交叉编译-CSDN博客 关键点&#xff1a;编译脚本&#xff0c;放在qt源代码根目录的.sh文件 #!/bin/shcd ./qt-everywhere-src-5.12.9./configure -prefix /home/qsqya/compile/qt5.12.9/build \ -opensource \ -release \ -confirm-license…