C#中的委托和事件学习(续)

2019-05-12 07:23:05王冬梅

大家可以尝试一下,将委托变量的声明那行代码注释掉,然后取消下面事件声明的注释。此时程序是无法编译的,当你使用了event关键字之后,直接在客户端触发事件这种行为,也就是直接调用pub.NumberChanged(100),是被禁止的。事件只能通过调用DoSomething()来触发。这样才是事件的本意,事件发布者的封装才会更好。

就好像如果我们要定义一个数字类型,我们会使用int而不是使用object一样,给予对象过多的能力并不见得是一件好事,应该是越合适越好。尽管直接使用委托变量通常不会有什么问题,但它给了客户端不应具有的能力,而使用事件,可以限制这一能力,更精确地对类型进行封装。

NOTE:这里还有一个约定俗称的规定,就是订阅事件的方法的命名,通常为“On事件名”,比如这里的OnNumberChanged。

为什么委托定义的返回值通常都为void?

尽管并非必需,但是我们发现很多的委托定义返回值都为void,为什么呢?这是因为委托变量可以供多个订阅者注册,如果定义了返回值,那么多个订阅者的方法都会向发布者返回数值,结果就是后面一个返回的方法值将前面的返回值覆盖掉了,因此,实际上只能获得最后一个方法调用的返回值。可以运行下面的代码测试一下。除此以外,发布者和订阅者是松耦合的,发布者根本不关心谁订阅了它的事件、为什么要订阅,更别说订阅者的返回值了,所以返回订阅者的方法返回值大多数情况下根本没有必要。

class Program {
   static void Main(string[] args) {
     Publishser pub = new Publishser();
     Subscriber1 sub1 = new Subscriber1();
     Subscriber2 sub2 = new Subscriber2();
     Subscriber3 sub3 = new Subscriber3();

     pub.NumberChanged += new GeneralEventHandler(sub1.OnNumberChanged);
     pub.NumberChanged += new GeneralEventHandler(sub2.OnNumberChanged);
     pub.NumberChanged += new GeneralEventHandler(sub3.OnNumberChanged);
     pub.DoSomething();     // 触发事件
  }
 }

// 定义委托
public delegate string GeneralEventHandler();

// 定义事件发布者
public class Publishser {
   public event GeneralEventHandler NumberChanged; // 声明一个事件
  public void DoSomething() {
     if (NumberChanged != null) {  // 触发事件
      string rtn = NumberChanged();
       Console.WriteLine(rtn);   // 打印返回的字符串,输出为Subscriber3
    }
   }
 }

// 定义事件订阅者
public class Subscriber1 { 
   public string OnNumberChanged() {
     return "Subscriber1";
   }
 }
public class Subscriber2 { /* 略,与上类似,返回Subscriber2*/ }
public class Subscriber3 { /* 略,与上类似,返回Subscriber3*/ }

如果运行这段代码,得到的输出是Subscriber3,可以看到,只得到了最后一个注册方法的返回值。

如何让事件只允许一个客户订阅?