正确调用事件处理程序
不管是刚接触 C# 还是已经具有多年开发经验的大部分人会觉得事件处理很简单,只需要把事件定义好然后在需要的时候出发它就可以了。其实这种想法是错误的,这里面有很多需要注意的问题。下面这段代码是大部分开发人员经常使用的定义事件处理程序的方法。
public class EventDemo
{
private EventHandler<int> demo;
public void DemoEvent()
{
demo(this);
}
}
上面的代码中存在一个严重的问题,当在对象上触发 demo 事件时并没有关联的事件处理程序的话,C# 将会用 null 值来表示没有处理程序与该事件相关联,进而将会引发 NullReferenceException 异常。针对这个问题大部分程序员会做如下修改:
public void DemoEvent()
{
if(demo!=null)
{
demo(this);
}
}
这种修改方法解决了上述大部分问题,但是还存在一个隐藏的问题。当有多个线程都调用这个事件是就会出现线程之间相互争夺,举个例子来说就是线程 A 在执行到 if (demo!=null)
时发现 demo 不等于 null ,正巧这时线程 B 将唯一的事件处理程序解除了订阅,这时线程 A 再调用 demo 时事件处理程序已经变为了 null ,进而导致 NullReferenceException 异常。真多这个问题一些程序员又会做如下的修改:
public void DemoEvent()
{
var handler = demo;
if(handler!=null)
{
handler(this);
}
}
上述这种方法是对等号右侧的内容进行了浅拷贝创建了新的引用,使其指向原来的事件处理程序(相当于给事件订阅者生成了一个快照),当另一个进程注销掉事件处理程序时,注销的只是 demo 上所绑定的处理程序,因此当当前的线程执行 handler 时是不会出现 NullReferenceException 异常。这种解决方法是网上所能搜的方法之一,也是绝大部分开发人员所推荐的解决方法。但是这个方法会使代码显得难以理解(尤其是对于开发新手),并且代码稍显冗余。于是在 C# 6.0 中微软为我们增加了 null 条件运算符(?.)。null 条件运算符可以安全的调用事件处理程序并且使代码清晰明了还简单。首先它会判断运算符左侧的内容是否为 null ,如果是 null 就跳过该语句,反之执行运算符右侧的内容。下面我们利用 null 条件运算符对前面的代码进行一下改进。
public void DemoEvent()
{
demo?.Invoke(this);
}
Tip:使用 null 条件运算符有一点需要注意,运算符右侧不允许直接出现括号,因此必须使用 Invoke 进行触发事件。每定义一个委托或者时间编译器就会生成一个 Invoke 方法。
进行触发事件。每定义一个委托或者时间编译器就会生成一个 Invoke 方法。
- Leetcode 278. First Bad Version
- Leetcode 275. H-Index II
- Leetcode 274. H-Index
- 值得 .NET 开发者了解的15个特性
- Angular和Vue.js 深度对比
- 前端开发者常用的9个JavaScript图表库
- 1000多个项目中的十大JavaScript错误以及如何避免
- SoapUI实践:自动化测试、压力测试、持续集成
- 如何把kotlin+spring boot开发的项目部署在tomcat上
- 使用开源项目Alipay.AopSdk.Core完成支付宝网页登录
- vhost-user 简介
- 把玩爬虫框架Gecco
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(55)-Web打印
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(75)-微信公众平台开发-用户管理
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- php给数组赋值的实例方法
- php实现分页功能的详细实例方法
- 浅谈Laravel中的三种中间件的作用
- laravel 使用auth编写登录的方法
- laravel框架 laravel-admin上传图片到oss的方法
- php实现推荐功能的简单实例
- php实现登录页面的简单实例
- Laravel 自带的Auth验证登录方法
- laravel实现上传图片的两种方式小结
- Laravel开启跨域请求的方法
- Laravel 5.4前后台分离,通过不同的二级域名访问方法
- Yii框架数据库查询、增加、删除操作示例
- 在laravel5.2中实现点击用户头像更改头像的方法
- laravel 多图上传及图片的存储例子
- Laravel 添加多语言提示信息的方法