成本章之后,你将能够:
- 声明代理类型来创建一个方法签名的抽象
- 创建一个托管到特定方法或匿名方法的代理实例
- 通过代理调用方法
- 使用代理来处理事件
- 引发事件
在这本书中不同的练习中,你所写过的多数代码理论上都是按顺序执行的,而这只是一般的情况,有时你会发现中断当前的流程去执行一个更重要的任务是很必要的,当这个任务完成我们的程序再从刚才中断的地方继续。windows窗体程序就是这种风格典型的例子:一个窗体显示按钮和文本框控件,当我们单击一个按钮或在一个文本框中键入文本时,我们期望窗体能立即做出反应。此时应用程序不得不临时停止它正在做的事情,过来处理你的输入。这样风格的操作不只运用在图形用户界面,对任何当需要迫切执行的操作应用都适合--比方说,当核电厂变得越来越热时要关闭反应堆。
注意 :
如果你对c++熟悉,那么代理和函数指针非常相似。然而不像函数指针的是:代理是类型安全的;而且你只能创建和代理所引用方法签名匹配的代理,指向一个无效方法的代理是不能被调用的。
自动化工厂情景
假设你正在为一个自动化工厂写一个控制系统,工厂里有大量各种不同的机器,每台机器在生产制造的过程中都执行独立的任务,如折叠整形金属板、焊接板子、给板子涂胶等等。每台机器由专门的厂商生产安装,机器都是可被计算机控制的,而且每个厂商还提供了一套api来控制他们的机器。你的任务就是把这些不同机器的系统集成到一个控制程序中,你已经决定集中解决的一个方面是在需要的情况下提供一种手段可以关闭所有的机器。每台机器都有自己唯一的控制流程和api来安全关闭机器,以下是一个简单的概要:
StopFolding(); // 折叠整形
machine FinishWelding(); // 焊接机器
PaintOff(); // 涂胶机器
不用代理的实现
在控制程序中实现关闭功能最简单的途径如下:
class Controller
{ ...
public void ShutDown()
{
folder.StopFolding();
welder.FinishWelding();
painter.PaintOff();
}
...
// Fields representing the different machines
private FoldingMachine folder;
private WeldingMachine welder;
private PaintingMachine painter;
}
尽管这个方法没错,但可扩展性和灵活性不是非常好,如果工厂买了一台新机器,你必需修改这个代码。要知道Controller 类和机器是紧耦合的。
事实上,尽管上面的方法名不想同,但他们有一个共性:即没有参数且都不返回值(稍后我们考虑如果不是这样怎么办,忍耐一下哦),每个方法都是通用的格式,因此如下:
void methodName();
这正是代理的用武之地,匹配这个共性的代理能够指向任意机器的关闭方法,我们可以这样来声明这个代理:
delegate void stopMachineryDelegate();
注意以下一点:
- 使用delegate声明一个代理
- 一个代理定义了它能引用的方法的原型:返回类型(void),代理的名称 (stopMachineryDelegate),和一些参数(这个例子没有参数)
定义完毕以后,我们就可以创建代理的实例并通过使用+=操作符来引用指定的方法,我们可以把它放到controller类的构造函数里面,如下:
class Controller
{
delegate void stopMachineryDelegate();
...
public Controller()
{
this.stopMachinery += folder.StopFolding;
}
...
private stopMachineryDelegate stopMachinery; // 创建一个代理的实例
}
这种语法我们需要逐渐适应,我们为代理添加了方法。这个时候我们还没有调用方法,+号操作符用在代理中被重载为新的意义(后边我们还会谈更多关于运算符重载的故事)。注意此处我们只指定方法名称,不应该包含圆括号或任何参数。
给一个未初始化的代理使用+=运算符是安全的,它将被自动初始化。你也可以用new关键字来显式初始化一个包含指定方法的代理,就象这样:
this.stopMachinery = new stopMachineryDelegate(folder.stopFolding);
此时我们能够通过调用代理来调用方法,如下:
public void ShutDown()
{
this.stopMachinery();
...
}
调用一个代理和调用一个方法的语法完全一样,如果代理引用的方法包含参数的话,此时你也应该指定它们。
注意: 如果你企图调用一个未初始化的代理,那么你就会得到一个空引用的异常(NullReferenceException) |
使用代理的主要优点是它能够引用不只一个方法,我们仅仅使用+=运算符就可以将方法添加进去,如:
public Controller()
{
this.stopMachinery += folder.StopFolding;
this.stopMachinery += welder.FinishWelding;
this.stopMachinery += painter.PaintOff;
}
现在我们在controller的shutdown方法中调用this.stopMachinery()就会依次调用其中的每一个引用方法了。shutdown方法不需要知道有多少机器,或者方法名是什么,我们还可以通过使用-=运算符从一个代理中移除一个方法。
-
建立代理变量stopMachinery并设置为public;public stopMachineryDelegate stopMachinery;
-
继续保持stopMachinery私有,但需要提供一个可读写的属性来访问它,当然这个代理类型也要声明为公共的。
public delegate void stopMachineryDelegate();
...
public stopMachineryDelegate StopMachinery
{
get
{
return this.stopMachinery;
}
set
{
this.stopMachinery = value;
}
}