委托确实是一个有趣的结构,它允许内存中的对象进行双向对话。然而,你可能会同意,从头使用委托会有一些重复代码(定义托委,声明必要的成员变量,以及创建自定义的注册/注销方法来保护封装等)。
除了时间之外,这样使用委托来作为应用程序的回调机制会有另一个问题是:如果我们没有反委托成员变量定义为私有的,调用者就可以直接访问委托对象。这样,调用者就可以把变量赋值为新的委托对象(实际上也就删除了当前要调用的方法列表),更糟糕的是,调用者可以直接调用委托的调用列表。为说明这个问题请看如下代码:
public class Car { //一个委托 public delegate void Exploded(string msg); //公共的,没有辅助方法 public Exploded explodedList; //触发分解的通知 public void Accelerate(int delta) { if(explodedList!=null) explodedList("Sorry,this car is dead.."); } }
我们不再有使用自定义的注册方法封装的私有委托成员变量。因为这些成员确实是公共的,调用者可以直接访问explodedList成员,把这个类型重新分配给新的Exploded对象并且随时调用委托:
class Program { static void Main(string[] args) { Console.WriteLine("Agh No Encapsulation!"); //创建一个Car Car myCar=new Car(); //我们可以直接访问委托 myCar.explodedList=new Car.Exploded(CallWhenExploded); myCar.acclerate(10); //现在可以赋值一个全新对象 myCar.explodedList=new Car.Exploded(CallHereToo); myCar.acclerate(10); //调用者还可以直接调用委托 myCar.explodedList.Invoke("hee,hee,hee"); } static void CallWhenExploded(string msg) {Console.WrtieLine(msg);} static void CallHereToo(string msg) {Console.WrtieLine(msg);} }
公共委托成员打破了封装,不仅会导致代码难以维护和调试,还会导致应用程序安全风险!显然,我们不希望给其它应用程序必变委托指向的权力以及没有我们的许可直接调用成员的权力。
enent关键字
为了简化自定义方法的构建来为委托调用列表增加和删除方法,C#提供了event关键字。在编译器处理event关键字的时候,它会自动提供注册和注销的方法以及委托类型任何必要的成员变理。这些委托成员变量总是声明为私有的,因此不能直接从触发事件的对象访问它们。可以肯定的是,event关键字就像一块语法糖,只是节省了我们打字的时间。
定义一个事件分为两个步骤。首先,我们需要定义一个委托,它包含在事件触发时将要调用的方法。其次,通过C# event关键字用相关委托声明这个事件。
Car类型事件会取与前面的调用者(AboutToBelow和Exploded)同样的名字。事件相关联的调用者会被命名为CarEventHandler。下面是对Car类型的第一次修改:
public calss Car { //这个委托用来与Car的事件协作 public delegate void CarEventHandler(string msg); //定义每个委托类型的成员变量 public event CarEventHandler Exploded; public event CarEventHandler AboutToBlow; ... }
向调用者发送一个事件,就如通过名称和相关联委托定义的必需参数来指定事件这么简单。为确保调用者注册事件,需要在调用委托的方法之前检查这个事件是否是无效值。了解这些后,下面来看修改后的Car的Accelerate()方法:
public void Accelerate(int delta) { //如果汽车不能用了,触发引爆事件 if(carIsDead) { if(Exploded!=null) { Exploded("sorry,this card is dead..."); } } else { currSpeed+=delta; //已经不能用了吗? if(10==maxSpeed-currSpeed&&AboutToBlow!-null_ { AboutToBlow("Careful boddy! Gonna blow!"); } //还好着呢 if(currSpeed>=maxSpeed) carIsDead=true; else Console.WriteLine("CurrSpeed={0}",currSpeed); } }
这样,我们已经设定了Car对象发送自定事件,这不再需要定义自定义注册函数,也不需要声明托委成员变量。
临听传入的事件
C#事件也简化了注册调用者事件处理程序的操作。现在调用者仅需使用+= 和-=运算符即可。
class Program { static void main(string[] args) { Console.Write("####Delegates as events #### "); Car cl=new Car("SlugBug",100,10); //注册事件处理程序 cl.OnAboutToBlow +=new Car.CarEventHandler(CarIsAlmostDoomed); cl.OnAboutToBlow +=new Car.CarEventHandler(CarAboutToBlow); cl.CarEventHandler d=new Car.CarEventHandler(CarExploded); c1.Exploded +=d; Console.WriteLine("Speed up"); for(int i=0;i<7;i++) cl.Accelerate(20); //从调用列表中移除CarExploded方法 c1.Exploded -=d; Console.WriteLine("Speed up"); for(int i=0;i<7;i++) cl.Accelerate(20); Console.ReadLine(); } public static void CarAboutToBlow(string msg) { Console.WriteLine(msg); } public static void CarIsAlmostDoomed(string msg) { Console.WriteLine(msg); } public static void CarExploded(stirng msg) { Console.WriteLine(msg);} }