Live软件开发面面谈
上QQ阅读APP看书,第一时间看更新

2.4 C#中的事件编程

人在某个环境某种风俗下生活,容易把这些环境和风俗看作是理所当然的。只有当一个人学习了一门外语后,他才能体会到母语的特点。同样的道理,Java语言里事件编程的模式,在和其他语言里的事件编程对照后,才能显示得更清晰。本节将看到常被用来和Java进行比较以及与之竞争的C#怎样以不同的理念处理事件编程中遇到的问题。

2.4.1 代理

从之前的讨论可以看出,事件编程的核心是发布者接收和运行以参数传入的事件处理逻辑,而该逻辑最简单和自然的载体就是一个回调函数。C和C++中的函数指针因为缺乏类型检查而不安全,Java选择放弃该功能。为了能传递方法又确保安全,Java 8用函数式接口把方法包装起来。另一种思路则是直接以某种形式对方法进行类型检查,C#(严格地说,是.NET编程框架)引入的代理(Delegate)就是以此为目的。代理是一种特殊的类型,用来设定单个方法的参数和返回类型,就像接口设定类的方法名称和签名。以它作为方法的类型,方法就能在通过编译时检查的前提下充当一级函数。随着C#的演进,代理的声明和初始化越来越简洁和方便,并且成为C#中的匿名方法和Lambda表达式背后的类型,是用C#进行函数式编程的坚实基础。下面的代码样例简明地显示了代理的演进。

Java和C#这两门分别由大公司开发和维护的,都属于C语言家族的,语法上有很多相似性的强类型语言,在相互竞争又彼此借鉴的演进过程中,陆续拥有很多对应的功能,比如函数式接口对代理、标记对元数据、双方的对象容器框架、双方的泛型方案,比较这些本质上相同的功能在两种类似的语言中设计上的差异,是一件饶有兴味的事情。

2.4.2 事件

代理有一个子类——多播代理(Multicast Delegate),能用+、-操作符将多个同类型的代理进行组合,当它被调用时,其成员会按照组合时的顺序逐个被调用。可以看出,多播代理很适合用来保存事件的处理函数。C#又专门引入了event关键字来定义事件,event添加在一个多播代理类型之前,表示事件的处理函数必须符合该类型。收听者可以向发布者公开的事件自由增删符合条件的处理函数,而只有在发布者内部才能调用这些函数。与Java相同的是,C#中的事件信息对象仍然有专门的类型EventArgs,特定事件可以创建继承自它的子类型。有了这些新发明的脚手架,C#中的事件编程可以直接将事件匹配到处理函数,发布者和收听者的关系可以极为灵活,发布者的某个事件可以由任何对象中的函数处理,收听者可以包含任何发布者的任意选定的事件的处理函数。因此C#从一开始就没有用Java时遇到的问题,而且比函数式接口更接近函数式编程本质的代理,用于事件编程时也比前者更简洁。演示这些概念最好的方法就是代码样例。

下面将2.3节中的鼠标事件的例子用C#改写,以资比较。