对.net事件的看法

一、事件的本质

事件是软件系统里的两个子系统之间,或者两个模块之间,或者两个对象之间发送消息,并处理消息的过程。在面向对象的世界里,就可以统一认为是两个对象之间的行为。

两个对象之间发送的这种消息,对发送方来讲是产生一个事件,对接受方来讲是需要处理某个事件。这种消息可以是用户操作产生的或者软件系统里的某个对象产生的。

对象之间的事件处理
 

从上图可见,对象一产生一个事件,这个事件发生以后需要对象二执行某种动作。这就是事件机制。对象一是事件的产生者,或者发送者;对象二是事件的接收者或者订阅者。对象一产生某种消息,需要对象二响应并处理这给消息,这就是事件的本质。

以往的很多软件系统都在采用事件机制处理很多问题。例如从最本质的计算机体系中的软中断处理,到masm中的jump,到c/c++中的回调函数等等。只不过越高级的软件系统处理事件或者其提供的很多处理方法越接近人的思维,而越远离机器思维。构建软件系统的方法从本质上就是从机器思维走向人的思维的过程。

二、事件机制的好处

1、直接调用

采用事件机制有什么好处?事件发送者为什么不直接调用事件接受者提供的处理函数呢?

调用机制
 

如果所示,两个对象之间的调用机制。对象B调用对象A的方法,可以通过函数指针或者跳转(汇编语言)等实现。这种方法造成的结果是A和B的紧密耦合,即B对A有很强的依赖性。可以看成B是事件的发布者,A是事件的响应和处理者。不过这种机制用事件机制解释从理论上就比较牵强了。同一种事物,其实现的思想不一样。

现在假设有个对象C也要响应B的事件。那么,按照上面的这种机制,需求修改对象B的代码,调用对象C的方法。这样机制造成了非常强的依赖关系。代码的修改和扩展非常麻烦。如果对象越多,这种关系越多,整个系统越复杂。如果一个系统里面对象很多,这种依赖关系也很多的情况下,这种调用关系就会十分复杂,对系统的健壮性和优良性会造成影响。

2、回调机制

如果按照c#的委托思想,B需要事先提供对事件处理函数的某些回调指针。这样,其它对象,例如A和C就去修改它的回调指针,把自己的方法联系到上面。但是它们之间的耦合关系就比上面简单了。

回调机制
 

回调机制的思想已经比较接近委托的概念。其实委托在本质上也就和回调指针差不多,只是概念上更加高级。对象B作为事件的发布者,事先定义一些回调函数指针,然后在本地合适的地方调用这些指针指向的函数。而事件订阅者或者处理者A和C所作的就是让给这些空指针赋值,把自己的事件处理方法赋给它,从而实现B调用A和C的方法。

在 C 或 C++ 中与委托最为相似的是函数指针。然而,函数指针只能引用静态函数,而委托可以引用静态方法和实例方法。当委托引用实例方法时,委托不仅存储对方法入口点的引用,还存储对为其调用该方法的类实例的引用。与函数指针不同,委托是面向对象、类型安全并且安全的。

三、事件机制的实现

1、委托的局限

如果单纯用委托,对于事件的发布者B来说,假设它发布事件e,对于事件e,它目前已经知道有A和C对象需要订阅这个事件。所以,它就申明两个委托对象引用(本质上类似于函数指针),然后让A和C对象来采用类似回调的机制订阅和响应事件。

如果后来,有个对象D也需要订阅B的事件e,它怎么办呢?一种情况是D修改B的一个委托对象引用,把自己的处理方法包装成一个委托对象付给它。这样,D就抢夺了A或者C的订阅。否则,就需要修改B的代码,添加一个类似的委托对象引用,以便让D来使用。

这样做的后果是事件发布者B需要申明很多委托对象的引用变量。结果是弄得代码维护比较混乱,并且使用者也很多,依赖关系也不容易搞清楚,容易发生错误。

2、事件的引入

有了委托,就提供了类似回调一样的功能。但是,回调机制需要事件发布者和事件订阅者双方的共同参与和努力。也就是,每增加一个订阅者,那么发布者对象就需要提供一个委托引用,让订阅者挂钩。

如果事件的发布者发布一个事件以后就不在关心谁来订阅它,那么以后的处理就交给了使用者,而发布者不再关心事件处理者的问题。

订阅机制
 

C#事件的事件就是这种订阅机制,真正的订阅。发布者不需要关心订阅者。

C#事件给订阅者提供了对事件响应的注册和反注册功能。订阅和撤销完全是事件接受方的行为。

C#事件机制的实现包括以下几步:

1、 事件发布者定义一个委托类型;

2、 事件发布者定义一个事件,并且关联到已经定义的委托上。

3、 事件订阅者需要产生一个委托实例,并把它添加到委托列表。

所以,事件event可以看成是一个事件列表,订阅者可以注册和撤销自己的响应和处理机制,但是它没有办法更改整个列表(原则上)。所以,提供了更强、更安全的方式。

四、事件机制的代码实例

应用程序结构图
 

如图所示,事件发布对象发布一个事件;事件订阅对象订阅和处理该事件。

using System;

namespace EventExample

{

///<summary>

/// MainClass : 主应用程序类

///</summary>

class MainClass

{

///<summary>

///应用程序的主入口点。

///</summary>

[STAThread]

static void Main(string[] args)

{

EventPublisher publisher = new EventPublisher();

EventReader1 reader1 = new EventReader1(publisher);

EventReader2 reader2 = new EventReader2(publisher);

publisher.DoSomthing();

Console.WriteLine("This program already finished!");

Console.ReadLine();

}

}

///<summary>

/// EventPublisher : 事件的发布者。

///</summary>

public class EventPublisher

{

// 第一步是申明委托

public delegate int sampleEventDelegate(string messageInfo);

// 第二步是申明与上述委托相关的事件

public event sampleEventDelegate sampleEvent;

public EventPublisher()

{

}

public void DoSomthing()

{

/* ... */

// 激发事件

if(this.sampleEvent != null)

{

this.sampleEvent("hello world!");

}

/* ... */

}

}

///<summary>

/// EventReader1 : 事件的订阅者1。

///</summary>

public class EventReader1

{

public EventReader1(EventPublisher publisher)

{

publisher.sampleEvent +=

new EventExample.EventPublisher.sampleEventDelegate(ResponseEvent);

}

private int ResponseEvent(string msg)

{

Console.WriteLine(msg + " --- This is from reader1");

return 0;

}

}

///<summary>

/// EventReader2 : 事件的订阅者2。

///</summary>

public class EventReader2

{

public EventReader2(EventPublisher publisher)

{

publisher.sampleEvent +=

new EventExample.EventPublisher.sampleEventDelegate(ResponseEvent);

publisher.sampleEvent +=

new EventExample.EventPublisher.sampleEventDelegate(ResponseEvent);

}

private int ResponseEvent(string msg)

{

Console.WriteLine(msg + " --- This is from reader2");

Console.WriteLine("Please:down enter key!");

Console.ReadLine();

Console.WriteLine("ok");

return 0;

}

}

}

程序运行结果
 

总结:事件发布者发布的事件在实质上可以看成对外提供的回调函数指针列表。这个列表的容量可以动态增长。事件订阅者可以把自己的事件注册到这个列表或者撤销注册,但是它从原则上无法更改或者对其它订阅者的注册产生影响。事件发布者通过两种手段使得订阅者正确地使用事件机制:一是定义一种delegate委托类型,事件订阅者只能按照这种类型定义事件的处理方法;二是定义与这个委托相关的event对象,使得订阅者只负责注册和撤销自己的处理过程而不能随意对别人的处理过程产生影响。

从运行结果和reader2对象把同一个处理方法注册了两次的前提可以看到,对于一个事件,同一个订阅者可以把同一个处理过程注册多次,而这个方法最终也会被执行多次。

执行事件订阅列表中方法的顺序不能被保证;而且,在这里采用的是同步调用方法,只有一个响应函数执行完毕,其它函数才会被执行。如果要方法不被阻塞(包括这里的等待用户输入等),就需要采用异步调用方式。

对.net事件的看法的更多相关文章

  1. 浅谈Qt事件的路由机制:鼠标事件

    请注意,本文是探讨文章而不是教程,是根据实验和分析得出的结果,可能是错的,因此欢迎别人来探讨和纠正. 这几天对于Qt的事件较为好奇,平时并不怎么常用,一般都是用信号,对于事件的处理,一般都是需要响应键 ...

  2. C#的Invoke和BeginInvoke

    在Invoke或者BeginInvoke的使用中无一例外地使用了委托Delegate,至于委托的本质请参考我的另一随笔:对.net事件的看法. 一.为什么Control类提供了Invoke和Begin ...

  3. 谈C#中的Delegate

    引言 Delegate是Dotnet1.0的时候已经存在的特性了,但由于在实际工作中一直没有机会使用Delegate这个特性,所以一直没有对它作整理.这两天,我再度翻阅了一些关于Delegate的资料 ...

  4. 每天学点管理学知识——情绪ABC理论

    什么是ABC理论 ABC理论(ABC Theory of Emotion)是由美国心理学家埃利斯创建的.就是认为激发事件A(activating event 的第一个英文字母)只是引发情绪和行为后果C ...

  5. C#学习之在辅助线程中修改UI控件----invoke方法

    Invoke and BeginInvoke 转载地址:http://www.cnblogs.com/worldreason/archive/2008/06/09/1216127.html 在Invo ...

  6. 关于MongoDB安全事件的一些思考

    刚刚过去的这个周末,各位大数据和数据库从业者想必是被MongoDB的"安全事件"给刷屏了,MongoDB作为当前NoSQL在全球的领军人物,遭到这么大规模的黑客攻击,这也再次让我们 ...

  7. [LeetCode] Split Linked List in Parts 拆分链表成部分

    Given a (singly) linked list with head node root, write a function to split the linked list into k c ...

  8. 人手一份核武器 - Hacking Team 泄露(开源)资料导览手册

    https://zhuanlan.zhihu.com/p/20102713 author:蒸米 0x00 序 事先声明本人并不是全栈安全工程师,仅仅是移动安全小菜一枚,所以对泄漏资料的分析难免会有疏忽 ...

  9. 2018(5)软件架构设计,架构风格,REST

    2018上半年系统分析师试题五 阅读以下关于Web应用设计开发的描述,在答题纸上回答问题1至问题3. [说明] 某公司拟开发一个自由,可定制性强.用户界面友好的在线调查系统,以获取员工在课程学习.对公 ...

随机推荐

  1. 【cocos2d-x 手游研发小技巧(4)与Android混编实现换“头像图片”】

    cocos2dx在android平台上的游戏开发中往往会遇到一些混编需求,如: 比方有的社区类游戏需要用到更换玩家的“头像”操作,其实就是调用android servers服务里面的本地图片,以及选取 ...

  2. Android intent 传值不更新的原因和解决办法

    当 Activity 的启动模式是 singleTask 或者 singleInstance 的时候.如果使用了 intent 传值,则可能出现 intent 的值无法更新的问题.也就是说每次 int ...

  3. GO学习笔记 - map

    map是GO语言中的一种高级数据类型,特点是key和value对应,这和Delphi中的Dictionary一样!map的声明格式:map[key数据类型]value数据类型.使用map前,必须用ma ...

  4. 重新使用Eclipse建立安卓工程遇到的问题

    很早之前用过Eclipse建立安卓工程,很久没用了,最近打算用Eclipse开发安卓程序,我是用谷歌提供的Eclipse集成环境建立的安卓工程,发现有了一些变化,而且遇到一点问题,这几天不断学习,终于 ...

  5. day00 -----博客作业1

    问题1.使用while循环输入 1 2 3 4 5 6     8 9 10 i = 0 while i<10: i+=1 if i ==7: continue print(i) 问题2 求1- ...

  6. eclipse远程debug服务器上的项目(Tomcat),打开、关闭及常见错误汇总

    我们工作中,有时候测试/生产环境,出现的结果会与我们预计的不一样,只看代码又看不出去问题所在,这个时候就需要远程debug下服务器上的项目. 注意:(1)需要debug的代码,本机代码需与服务器上一致 ...

  7. 【Quartz】工作原理

    本文参考至http://www.cnblogs.com/davidwang456/p/4205237.html和https://blog.csdn.net/guolong1983811/article ...

  8. 【JavaScript】动态原型模式创建对象 ||为何不能用字面量创建原型对象?

    var proto = ""; function Person(name, age, job) { this.name = name; this.age = age; this.j ...

  9. C#-WebForm-LinQ(一)-LinQ:语言集成查询(Language Integrated Query)-增删改查、属性扩展

    LinQ-语言集成查询(Language Integrated Query) 高集成化的数据库访问技术 LINQ 2 SQL 实际是将数据库的表映射成程序中的类 会把数据库的表名原封不动的变成类名 数 ...

  10. DataTables复杂表头

    工作上的需要,要做一个复杂的表头的DataTables thead如下 遇到的问题(详细问题可以浏览官网的答案 链接) 需自定义表头(thead),如果不自定义则会 Cannot read prope ...