.NET边学边讲(三)
你当前的位置:烁空 --> 技术文档全集
谈到EVENT,就不能不先说一下CALLBACK和DELEGATE
如果你使用过C的话,你应该知道有一个函数叫QSORT,是用来给数组排序的。但这个函数显然不能承担广泛意义上的比较,因此你需要传递一个指针,他指向具有比较功能的函数。QSORT在每次要比较数组元素时都要调用这个函数。这就是CALLBACK的概念,在.NET里也可以实现回调,方法是创建一个接口,实现他,传递一个实现此接口的对象的引用。DELEGATE呢,你可以将他理解成一个安全的函数指针。
NOTIFICATIONS跟CALLBACK有点类似,但比简单的回调要复杂的多。CALLBACK意味着要调用的CALLBACK方法被调用的同时要调用的建立CALLBACK的方法。这是一个很紧密的耦合。而NOTIFICATION则要松散一些,你可以注册将来某段时间会或者不会发生的NOTIFICATION,只当他们发生时处理,否则不用。
你也许想让你写的组件当一些事情发生时通知其他组件,例如,你想写一个按钮组件,当你CLICK的时候你可能想通知其他组件,而其他组件将不得不准备向你请求NOTIFICATION,你就要提供一个方法告诉他们你已经有一个可用的NOTIFICATION。另一方面,你可能也是当其它组件的一些事情发生时希望被通知的人。这时你就需要找到那个特定组件可以提供什么NOTIFICATION。
在.NET中EVENT是一个你用来广播、引发、处理NOTIFICATION的机制。大致是这样的,可以引发事件的组件声明这一事件。而希望处理某一特定组件的某一特定事件的组件通过传递一个方法的DELEGATE向引发事件的组件注册。这样,当事件发生时引发事件的组件就会调用每个已注册的方法。通过DELEGATE和EVENT我们可以实现异步调用的功能。在C#中是这样声明一个代理的:PUBLIC DELEGATE VOID LOGHANDLER(STRING MESSAGE);代理在处理这种回调时已经是很强大了。但是当我们需要代理被存储以便以后的NOTIFICATION,就有一点麻烦了。比如说我们有一个对象BUTTON,有一个CLICK事件。我们可以声明一个CLICKHANDLER 的代理类型用于处理CLICK事件,在我们的BUTTON的CLASS中声明一个CLICKHANDLER的PUBLIC实例,这样其他组件希望CLICK发生时被通知,就可以简单的把他的代理加到CLICK代理中去。MYBUTTON.CLICK += NEW CLICKHANDLER(MYMETHOD);
看上去着好像没什么问题。但是这里却存在一个大问题,我们声明CLICK代理是PUBLIC,这违反了我们以前说过的DATA FIELDS永远不要声明成PUBLIC,这会有一系列麻烦。解决的办法是声明成PRIVATE或PROTECTED,然后用属性解决读写。这样我们可以PRIVATE声明CLICK,在写一对PUBLIC方法去增加一个LISTENER及减少一个LISTENER。当然在.NET中,当你声明一个EVENT时,.NET已经为你做好这一切了。声明一个事件:
CLASS ALARMTIMER {
PUBLIC EVENT EVENTHANDLER ALARM;
// ...
}
这段代码说明ALARMTIMER可以向所有其它对象广播它可以引发一个叫ALARM的事件。ALARM事件用的是EVENTHANDLER代理类型。EVENTHANDLER:无返回值、接受两个参数(OBJECT:指向事件的发送者,EVENTARGS:包含关于事件的数据)
看一个例子:
CLASS ALARMTIMER {
PUBLIC EVENT EVENTHANDLER ALARM;
PRIVATE TIMER MYTIMER;
PUBLIC ALARMTIMER() {
MYTIMER = NEW TIMER();
MYTIMER.TICK += NEW EVENTHANDLER(ONTICK);
}
PUBLIC VOID SET(DOUBLE SECONDS) {
MYTIMER.INTERVAL = (INT32)(SECONDS * 1000);
MYTIMER.START();
}
PROTECTED VOID ONTICK(OBJECT SENDER, EVENTARGS E) {
MYTIMER.STOP();
IF (ALARM != NULL) ALARM(THIS, EVENTARGS.EMPTY);
}
PUBLIC VOID REENABLE() {
MYTIMER.ENABLED = TRUE;
}
}
注意ALARMTIMER既引发事件(ALARM)又处理事件(TIMER中的TICK事件)
STATIC ALARMTIMER MYALARM = NEW ALARMTIMER();
PUBLIC STATIC VOID TESTEVENT() {
MYALARM.ALARM += NEW EVENTHANDLER(TIMEREVENTPROCESSOR);
MYALARM.SET(2);
CONSOLE.WRITELINE("TIMER IS SET; ALARM WILL GO OFF IN TWO SECONDS");
APPLICATION.RUN();
}
//处理事件
PRIVATE STATIC VOID TIMEREVENTPROCESSOR(
OBJECT MYOBJECT, EVENTARGS MYEVENTARGS) {
IF (MESSAGEBOX.SHOW("WAKE UP! CONTINUE RINGING",
"COUNT IS: " + ALARMCOUNTER,
MESSAGEBOX.YESNO) == DIALOGRESULT.YES) {
ALARMCOUNTER += 1;
MYALARM.REENABLE();
}
ELSE {
APPLICATION.EXIT();
}
}
注意:OBJECT和EVENTARGS不是必需的参数,只是这是一个好的写法模式而已
关于EVENT还有好多没有说,留着以后慢慢说吧。