惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
小众软件
小众软件
爱范儿
爱范儿
Y
Y Combinator Blog
博客园 - Franky
美团技术团队
博客园 - 【当耐特】
The Cloudflare Blog
罗磊的独立博客
Hugging Face - Blog
Hugging Face - Blog
Jina AI
Jina AI
IT之家
IT之家
人人都是产品经理
人人都是产品经理
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
大猫的无限游戏
大猫的无限游戏
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 聂微东
WordPress大学
WordPress大学
V
Visual Studio Blog
博客园_首页
阮一峰的网络日志
阮一峰的网络日志
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
有赞技术团队
有赞技术团队

博客园 - Nio

Oracle Exception: Could not load file or assembly ‘Oracle.DataAccess, Version=2.111.7.20… [转载] 常用第三方开源上传组件总结 Dev Express google doc 无法访问解决 NHibernate 查詢多表返回到IList<Model>中 用 HTML 格式导出 Excel 时,如何保留显示网格线 清除 SQL SERVER 2005 事务日志 [原]Excel与OWC做报表总结 owc控件不能在win server 2003 64位中使用 禁用浏览器后退按钮方案 asp.net连接数据库(SQL Server 2005 Express)详细说明 js 中replace的用法 - Nio - 博客园 当前标识没有对"Temporary ASP.NET Files"的写访问权限的解决办法 SqlServer 205 Express 常见问题 在模态窗口中提交后,如何关闭窗口再刷新父页面 Reporting Services 在程式中部署 .NET 事件模型教程(二)转载 .NET 事件模型教程(一)转载 .net Resource
.NET 事件模型教程(三)转载
Nio · 2008-04-18 · via 博客园 - Nio

通过前两节的学习,你已经掌握了 .NET 事件模型的原理和实现方式。这一节我将介绍两个替代方案,这些方案并不是推荐采用的,请尽量采用事件模型去实现。另外,在本节末尾,有一段适合熟悉 Java 语言的读者阅读,讨论了 .NET 和 Java 在“事件模型”方面的差异。

目录

使用接口实现回调

事件模型其实是回调函数的一种特例。像前面的例子,Form1 调用了 Worker,Worker 反过来(通过事件模型)让 Form1 改变了状态栏的信息。这个操作就属于回调的一种。

在“.NET Framework 类库设计指南”中提到了:“委托、接口和事件允许提供回调功能。每个类型都有自己特定的使用特性,使其更适合特定的情况。”(参见本地 SDK 版本,在线 MSDN 版本

事件模型中,事实上也应用了委托来实现回调,可以说,事件模型是委托回调的一个特例。如果有机会,我会在关于多线程的教程中介绍委托回调在多线程中的应用。

这里我先来看看,如何使用接口实现回调功能,以达到前面事件模型实现的效果。

Demo 1I:使用接口实现回调。

using System;
using System.Threading;
using System.Collections;
namespace percyboy.EventModelDemo.Demo1I
{
// 注意这个接口
public interface IWorkerReport
{
void OnStartWork(int totalUnits);
void OnEndWork();
void OnRateReport(double rate);
}
public class Worker
{
private const int MAX = Consts.MAX;
private IWorkerReport report = null;
public Worker()
{
}
// 初始化时同时指定 IWorkerReport
public Worker(IWorkerReport report)
{
this.report = report;
}
// 或者初始化后,通过设置此属性指定
public IWorkerReport Report
{
set { report = value; }
}
public void DoLongTimeTask()
{
int i;
bool t = false;
double rate;
if (report != null)
{
report.OnStartWork( MAX );
}
for (i = 0; i <= MAX; i++)
{
Thread.Sleep(1);
t = !t;
rate = (double)i / (double)MAX;
if (report != null)
{
report.OnRateReport( rate );
}
}
if ( report != null)
{
report.OnEndWork();
}
}
}
}

你可以运行编译好的示例,它可以完成和前面介绍的事件模型一样的工作,并保证了耦合度没有增加。调用 Worker 的 Form1 需要做一个 IWorkerReport 的实现:

        private void button1_Click(object sender, System.EventArgs e)
{
statusBar1.Text = "开始工作 ....";
this.Cursor = Cursors.WaitCursor;
long tick = DateTime.Now.Ticks;
Worker worker = new Worker();
// 指定 IWorkerReport
worker.Report = new MyWorkerReport(this);
worker.DoLongTimeTask();
tick = DateTime.Now.Ticks - tick;
TimeSpan ts = new TimeSpan(tick);
this.Cursor = Cursors.Default;
statusBar1.Text = String.Format("任务完成,耗时 {0} 秒。", ts.TotalSeconds);
}
// 这里实现 IWorkerReport
private class MyWorkerReport : IWorkerReport
{
public void OnStartWork(int totalUnits)
{
}
public void OnEndWork()
{
}
public void OnRateReport(double rate)
{
parent.statusBar1.Text = String.Format("已完成 {0:P0} ....", rate);
}
private Form1 parent;
public MyWorkerReport(Form1 form)
{
this.parent = form;
}
}

你或许已经觉得这种实现方式,虽然 Worker 类“里面”可能少了一些代码,却在调用时增加了很多代码量。从重复使用的角度来看,事件模型显然要更方便调用。另外,从面向对象的角度,我觉得理解了事件模型的原理之后,你会觉得“事件”会更亲切一些。

另外,IWorkerReport 中包含多个方法,而大多时候我们并不是每个方法都需要,就像上面的例子中那样,OnStartWork 和 OnEndWork 这两个都是空白。如果接口中的方法很多,也会给调用方增加更多的代码量。

下载的源代码中还包括一个 Demo 1J,它和 Worker 类一起,提供了一个 IWorkerReport 的默认实现 WorkerReportAdapter(每个方法都是空白)。这样,调用方只需要从 WorkerReportAdapter 继承,重写其中需要重写的方法,这样会减少一部分代码量。但我觉得仍然是很多。

注意,上述的代码,套用(仅仅是套用,因为它不是事件模型)“单播事件”和“多播事件”的概念来说,它只能支持“单播事件”。如果你想支持“多播事件”,我想你可以考虑加入 AddWorkerReport 和 RemoveWorkerReport 方法,并使用 Hashtable 等数据结构,存储每一个加入的 IWorkerReport。

[TOP]

.NET 事件模型和 Java 事件模型的对比

(我对 Java 语言的了解不是很多,如果有误,欢迎指正!)

.NET 的事件模型,对于 C#/VB.NET 两种主流语言来说,是在语言层次上实现的。C# 提供了 event 关键字,VB.NET 提供了 Event,RaiseEvent 关键字。像前面两节所讲的那样,它们都有各自的声明事件成员的语法。而 Java 语言本身是没有“事件”这一概念的。

从面向对象理论来看,.NET 的一个类(或类的实例:对象),可以拥有:字段、属性、方法、事件、构造函数、析构函数、运算符等成员类型。在 Java 中,类只有:字段、方法、构造函数、析构函数、运算符。Java 的类中没有属性和事件的概念。(虽然 Java Bean 中将 getWidth、setWidth 的两个方法,间接的转换为一个 Width 属性,但 Java 依然没有把“属性”作为一个语言层次的概念提出。)总之,在语言层次上,Java 不支持事件。

Java Swing 是 Java 世界中常用的制作 Windows 窗体程序的一套 API。在 Java Swing 中有一套事件模型,来让它的控件(比如 Button 等)拥有事件机制。

Swing 事件模型,有些类似于本节中介绍的接口机制。它使用的接口,诸如 ActionListener、KeyListener、MouseListener(注意:按照 Java 的命名习惯,接口命名不用前缀 I)等;它同时也提供一些接口的默认实现,如 KeyAdapter,MouseAdapter 等,使用方法大概和本节介绍的类似,它使用的是 addActionListener/removeActionListener,addKeyListener/removeKeyListener,addMouseListener/removeMouseListener 等方法,来增减这些接口的。

正像本节的例子那样,使用接口机制的 Swing 事件模型,需要书写很多的代码去实现接口或者重写 Adapter。而相比之下,.NET 事件模型则显得更为轻量级,所需的挂接代码仅一行足矣。

另一方面,我们看到 Swing 的命名方式,将这些接口都命名为 Listener,监听器;而相比之下,.NET 事件模型中,对事件的处理被称为 handler,事件处理程序。一个采用“监听”,一个是“处理”,我认为这体现了一种思维上的差异。

还拿张三大叫的例子来讲,“处理”模型是说:当张三大叫事件发生时,外界对它做出处理动作(handle this event);监听,则是外界一直“监听”着张三的一举一动(listening),一旦张三大叫,监听器就被触发。处理模型是以张三为中心的思维,监听模型则是以外部环境为中心的思维。