前面文章中介绍了委托相关的概念,委托实例保存这一个或一组操作,程序中将在某个特定的时刻通过委托实例使用这些操作。

如果做过GUI程序开发,可能对上面的描述会比较熟悉。在GUI程序中,单击一个button会触发一个click事件,然后会执行一系列的操作,这一系列的操作就被存放在一个委托实例中。

接下来我们就看看事件。

使用委托中的问题

回到前面文章中苹果和富士康的例子,苹果将iphone的组装、包装和运输的工作全部委托给了富士康。

根据上面的描述,我们修改了一下代码,在Apple这个类中加入一个订单属性,苹果只要接到新的订单,就发送一个通知给富士康,然后富士康就会执行一系列的操作了(组装、包装和运输)。

在主程序中,苹果将iphone的组装、包装和运输工作委托给了富士康,然后苹果每次收到订单,就会通过委托实例"VerdorToAssembleIphone"让富士康去执行一系列操作。

class Apple
{
//声明委托类型
public delegate void AssembleIphoneHandler(int num);
public AssembleIphoneHandler VerdorToAssembleIphone; public void DesignIphone()
{
Console.WriteLine("Design Iphone By Apple");
} private int orderNum;
public int OrderNum
{
get { return this.orderNum; }
set
{
this.orderNum = value;
if (VerdorToAssembleIphone != null)
{
VerdorToAssembleIphone(this.orderNum);
}
}
}
} class Foxconn
{
//与委托类型签名相同的方法
public void AssembleIphone(int num)
{
Console.WriteLine("Assemble {0} Iphone By Foxconn", num);
} public void PackIphone(int num)
{
Console.WriteLine("Pack {0} Ipnone By Foxconn", num);
} public void ShipIphone(int num)
{
Console.WriteLine("Ship {0} Iphone By Foxconn", num);
}
} class Program
{
static void Main(string[] args)
{
Apple apple = new Apple();
Foxconn foxconn = new Foxconn();
apple.VerdorToAssembleIphone = new Apple.AssembleIphoneHandler(foxconn.AssembleIphone);
apple.VerdorToAssembleIphone += new Apple.AssembleIphoneHandler(foxconn.PackIphone);
apple.VerdorToAssembleIphone += new Apple.AssembleIphoneHandler(foxconn.ShipIphone); apple.OrderNum = ; Console.Read();
}
}

下面我们看下这个例子实现中的问题:

  1. 如果用户在建立委托链的时候错误的使用了"="而不是"+=",那么委托链就断了
  2. 在主程序中,我们可以绕过设置订单这一步,直接调用委托实例"apple.VerdorToAssembleIphone(99);"来让富士康执行操作,这点是不合理的
class Program
{
static void Main(string[] args)
{
Apple apple = new Apple();
Foxconn foxconn = new Foxconn(); //创建委托实例
apple.AssembleIphone = new Apple.AssembleIphoneHandler(foxconn.AssembleIphone);
apple.AssembleIphone += new Apple.AssembleIphoneHandler(foxconn.PackIphone);
apple.AssembleIphone = new Apple.AssembleIphoneHandler(foxconn.ShipIphone);
apple.DesignIphone(); //委托实例的调用
apple.VerdorToAssembleIphone(); Console.Read();
}
}

事件的出现

为了解决上面两个问题,出现了事件这个概念,我们要做的改变只是在声明委托实例的时候加上event关键字。

public event AssembleIphoneHandler VerdorToAssembleIphone;

这时,上面两处有问题的代码就会在编译的时候报错了。

上面的问题是解决了,但是event关键字作用是什么,事件跟委托有什么关系,"VerdorToAssembleIphone"怎么理解?

深入理解事件

其实,下面这个语句还是比较难理解的,看到的第一反应就是,委托跟事件到底是什么关系,event关键字跟委托类型"AssembleIphoneHandler"是什么关系。

public event AssembleIphoneHandler VerdorToAssembleIphone;

其实,事件可以理解成一个委托的属性,通过对委托实例的封装来对委托实例的访问进行一些限制。下面我们通过IL来查看一下这个个程序,得到下图。

下面我们就结合IL的查看结果来分析事件到底是什么。在开始之前,相信大家一定都熟悉属性(property)这个概念吧,那就让我们从熟悉的属性开始分析。

C#属性的概念

首先来看看我们熟悉的东西,"OrderNum"是我们定义的一个订单数量的属性(property),它有一组get/set方法。通过这个属性的get/set方法,我们可以访问"orderNum"字段(field)。

通过IL查看"OrderNum"这个属性,我们可以看到这个属性对应的get/set方法。

public int32 get_OrderNum() { }
public void set_OrderNum(int32 'value') { }

事件

接下来再看"VerdorToAssembleIphone"事件,我们按照属性的方式去理解事件,从IL的截图中,可以看到事件中有一对addon/removeon方法来操作我们的委托实例(想想属性的get/set方法)。

同时,我们看到编译器给我们生成了一个private的field(如下),从这里我们可以看到事件"VerdorToAssembleIphone"本质上就是一个委托类型的变量。由于这个变量是private的,也就解释了为什么我们在定义事件的类外部不能直接访问这个变量。

.field private class _1_1_Delegate.Apple/AssembleIphoneHandler VerdorToAssembleIphone

事件代码的转换

根据上面的分析,我们可以看到,编译器帮我们进行了下面的代码转换。这样一来,就相当于通过event关键字对委托实例访问增加了一些限制。

原始的声明事件的C#代码:

public event AssembleIphoneHandler VerdorToAssembleIphone;

编译器转换后的代码:

private AssembleIphoneHandler VerdorToAssembleIphone;

public void add_VerdorToAssembleIphone(AssembleIphoneHandler 'value') { }

public void remove_VerdorToAssembleIphone(AssembleIphoneHandler 'value') { }

通过上面的分析,可以了解到事件封装了委托类型的实例,使得:

  • 在定义事件的类内部,不管你声明它是public还是protected,它总是private的;也就是说在在定义事件的类外部不能对事件进行调用
  • 在定义事件的类外部,添加"+="和移除"-="委托实例的访问限定符与声明事件时使用的访问符相同
    • 也就是说,如果事件声明为private或这protect, 那么定义事件的类外部也不能对事件进行"+="和"-="操作

事件编程

其实,上面的例子只是一个简单的演示。在很多情况下,事件使用过程中都会结合两个参数:事件源和事件参数。

所以,在事件编程中,可以参考下面一些规范:

  • 统一以EventNameEventHandler方式命名委托变量:EventName是事件的名称
  • delegate接受两个参数,参数名统一命名为sender和e:第一个参数类型是object,第二个参数是事件参数类型,以EventNameEventArgs命名,并且需继承于System.EventArgs类
  • 如果在事件中不需要传递任何数据,也需要声明两个参数:第一个参数就是默认的object sender,第二个参数可以使用系统默认的System.EventArgs类

总结

通过本文介绍了事件的概念以及原理,解释了通过事件如何封装委托实例,并解决委托例子中遇到的两个问题。

同时了解了事件的使用:

  • 通过event关键字声明事件,
    • <事件修饰> event <委托类型> <事件名称>;
  • 事件的调用,由于事件本质上是委托类型,调用事件与调用委托一样,但是事件的调用只能发生在定义事件的类的内部

理解C#事件的更多相关文章

  1. 通俗理解Android事件分发与消费机制

    深入:Android Touch事件传递机制全面解析(从WMS到View树) 通俗理解Android事件分发与消费机制 说起Android滑动冲突,是个很常见的场景,比如SliddingMenu与Li ...

  2. 理解DOM事件流的三个阶段

    本文主要解决两个问题: 1.什么是事件流 2.DOM事件流的三个阶段 事件流之事件冒泡与事件捕获 在浏览器发展的过程中,开发团队遇到了一个问题.那就是页面中的哪一部分拥有特定的事件? 可以想象画在一张 ...

  3. JavaScript:理解worker事件api

    如果你不是很了解Event事件,建议先看我上一篇随文javascript:理解DOM事件.或者直接看下文worker api. hack 首先,我们需要实例一个Worker的对象,浏览器会根据新创建的 ...

  4. javascript:理解DOM事件

    首先,此文不讨论繁琐细节,但是考虑到读者的心灵感受,本着以积极向上的心态,在此还是会列举示例说明. ​标题为理解DOM事件,那么在此拿一个简单的点击事件为例,希望大家看到这个例子后能触类旁通. DOM ...

  5. 深度理解DOM事件(实例)

    前言 通过如下两个实例来理解DOM事件 实例1--点击别处关闭浮层 onclick与addEventListener的区别 实例2--点击后颜色一层一个层出现的漂亮的彩虹圈 1  实例1--点击别处关 ...

  6. 简单理解yii事件

    https://blog.csdn.net/qq43599939/article/details/80363827  通过观察者来理解yii事件 测试 class TestController ext ...

  7. 深入理解Spring事件机制(一):广播器与监听器的初始化

    前言 Spring 从 3.x 开始支持事件机制.在 Spring 的事件机制中,我们可以令一个事件类继承 ApplicationEvent 类,然后将实现了 ApplicationListener ...

  8. 从click事件理解DOM事件流

    事件流是用来解释页面上的不同元素接受一个事件的顺序,首先要明确两点: 1.一个事件的影响元素可能不止一个(同心圆理论),但目标元素只有一个. 2.如果这些元素都绑定了相同名称的事件函数,我们怎么知道这 ...

  9. 深入理解javascript事件流

    摘要:事件流这个东西是比较重要的,为了让自己更加理解js中的事件流,必须整理整理,梳理一下事件流的各种东西啊.本文大部分内容参考<javascript高级程序设计第三版> 先来一段书里的原 ...

  10. 深入理解JavaScript 事件

    本文总结自<JavaScript高级程序设计>以及自己平时的经验,针对较新浏览器以及 DOM3 级事件标准(2016年8月),对少部分内容作了更正,增加了各种例子及解析. 如无特殊说明,本 ...

随机推荐

  1. jenkins 更换主数据目录

    工作中,由于Jenkins默认的主目录空间太小,导致需要将Jenkins默认的主目录修改到其它目录.本文针对更改Jenkins的主目录详细介绍. 注意:在Jenkins运行时是不能更改的. 请先将Je ...

  2. [转]轻松学习Ionic (四) 修改应用图标及添加启动画面(更新官方命令行工具自动生成)

    本文转自:http://blog.csdn.net/zapzqc/article/details/42237935 由于Ionic更新了命令行工具,以后修改应用图标和添加启动画面就简单了,最新方法见最 ...

  3. Scanner类总结

    1.构造器 Scanner(File source) Scanner(InputStream source) Scanner(String source) 参考:http://blog.sina.co ...

  4. notepad++下的字体设置

    设置 - 语言格式设置 中

  5. MySQL数据库学习笔记(八)----JDBC入门及简单增删改数据库的操作

    [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...

  6. IL查看override

    查看override的IL Override示例 下面我们看一个Override的Example namespace MyCollection { public class MyBase { publ ...

  7. rsync+inotify实时同步环境部署记录

    随着应用系统规模的不断扩大,对数据的安全性和可靠性也提出的更好的要求,rsync在高端业务系统中也逐渐暴露出了很多不足.首先,rsync在同步数据时,需要扫描所有文件后进行比对,进行差量传输.如果文件 ...

  8. 在centos下部署docker内网私服

    Docker内网私服:docker-registry with nginx & ssl on centos docker-registry既然也是软件应用,自然最简单的方法就是使用官方提供的已 ...

  9. js常见执行方法$(document).load(),$(document).ready()

    $(document).load(); 当web页面以及其附带的资源文件,如CSS,Scripts,图片等,加载完毕后执行此方法.常用于检测页面(及其附带资源)是否加载完毕. $(document). ...

  10. Linux下Qt的安装与配置

    参考资料:http://www.cnblogs.com/emouse/archive/2013/01/28/2880142.html Linux 下编译.安装.配置 QT 下载qt 这里用的是4.7. ...