正确调用事件处理程序

简介: 正确调用事件处理程序

不管是刚接触 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 方法。

目录
相关文章
|
4月前
|
JavaScript 前端开发
事件8
事件8
37 2
|
存储 JSON 前端开发
EventSource 引发的一系列事件 #150
EventSource 引发的一系列事件 #150
248 0
|
API 数据库
9.2领域事件
领域(近似理解为实现某个功能的多个模型)事件可以切断领域模型之间的强依赖关系,事件发布后,由事件的处理者决定如何响应事件,以便于实现事件发布和事件处理的解耦。
160 0
|
JavaScript 前端开发 安全
什么事件必须要我王二狗来处理?
什么事件必须要我王二狗来处理?
245 0
什么事件必须要我王二狗来处理?
|
安全 编译器 C#
使用null条件运算符调用事件处理程序
使用null条件运算符调用事件处理程序
115 0
|
小程序
小程序 事件
 什么是事件 事件是视图层到逻辑层的通讯方式。 事件可以将用户的行为反馈到逻辑层进行处理。 事件可以绑定在组件上,当达到触发事件,就会执行逻辑层中对应的事件处理函数。 事件对象可以携带额外信息,如 id, dataset, touches。
951 0
基于epoll封装的事件回调miniserver
epoll技术前两节已经阐述过了,目前主要做一下封装,很多epoll的服务器都是采用事件回调方式处理, 其实并没有什么复杂的,我慢慢给大家阐述下原理。 在networking.h和networking.cpp里,这两个文件主要实现了一些文件读写功能的回调函数 。
941 0