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

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

少数情况下,比如像上面,为了避免发生“值覆盖”的情况(更多是在异步调用方法时,后面会讨论),我们可能想限制只允许一个客户端注册。此时怎么做呢?我们可以向下面这样,将事件声明为private的,然后提供两个方法来进行注册和取消注册:

// 定义事件发布者
public class Publishser {
   private event GeneralEventHandler NumberChanged;  // 声明一个私有事件
  // 注册事件
  public void Register(GeneralEventHandler method) {
     NumberChanged = method;
   }
   // 取消注册
  public void UnRegister(GeneralEventHandler method) {
     NumberChanged -= method;
   }

   public void DoSomething() {
     // 做某些其余的事情
    if (NumberChanged != null) {  // 触发事件
      string rtn = NumberChanged();
       Console.WriteLine("Return: {0}", rtn);   // 打印返回的字符串,输出为Subscriber3
    }
   }
 }

NOTE:注意上面,在UnRegister()中,没有进行任何判断就使用了NumberChanged-=method语句。这是因为即使method方法没有进行过注册,此行语句也不会有任何问题,不会抛出异常,仅仅是不会产生任何效果而已。

注意在Register()方法中,我们使用了赋值操作符“=”,而非“+=”,通过这种方式就避免了多个方法注册。上面的代码尽管可以完成我们的需要,但是此时大家还应该注意下面两点:

1、将NumberChanged声明为委托变量还是事件都无所谓了,因为它是私有的,即便将它声明为一个委托变量,客户端也看不到它,也就无法通过它来触发事件、调用订阅者的方法。而只能通过Register()和UnRegister()方法来注册和取消注册,通过调用DoSomething()方法触发事件(而不是NumberChanged本身,这在前面已经讨论过了)。

2、我们还应该发现,这里采用的、对NumberChanged委托变量的访问模式和C#中的属性是多么类似啊?大家知道,在C#中通常一个属性对应一个类型成员,而在类型的外部对成员的操作全部通过属性来完成。尽管这里对委托变量的处理是类似的效果,但却使用了两个方法来进行模拟,有没有办法像使用属性一样来完成上面的例子呢?答案是有的,C#中提供了一种叫事件访问器(Event Accessor)的东西,它用来封装委托变量。如下面例子所示:

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

     pub.NumberChanged -= sub1.OnNumberChanged; // 不会有任何反应
    pub.NumberChanged += sub2.OnNumberChanged; // 注册了sub2
    pub.NumberChanged += sub1.OnNumberChanged; // sub1将sub2的覆盖掉了
    
     pub.DoSomething();     // 触发事件
  }
 }

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

// 定义事件发布者
public class Publishser {
   // 声明一个委托变量
  private GeneralEventHandler numberChanged;
   // 事件访问器的定义
  public event GeneralEventHandler NumberChanged {
     add {
       numberChanged = value;
     }
     remove {
       numberChanged -= value;
     }
   }
   
   public void DoSomething() {
     // 做某些其他的事情
    if (numberChanged != null) {  // 通过委托变量触发事件
      string rtn = numberChanged();
       Console.WriteLine("Return: {0}", rtn);   // 打印返回的字符串
    }
   }
 }

// 定义事件订阅者
public class Subscriber1 {
   public string OnNumberChanged() {
     Console.WriteLine("Subscriber1 Invoked!");
     return "Subscriber1";
   }
 }
public class Subscriber2 {/* 与上类同,略 */}
public class Subscriber3 {/* 与上类同,略 */}