C#中的委托和事件学习(续)
可见我们获得了三个方法的返回值。而我们前面说过,很多情况下委托的定义都不包含返回值,所以上面介绍的方法似乎没有什么实际意义。其实通过这种方式来触发事件最常见的情况应该是在异常处理中,因为很有可能在触发事件时,订阅者的方法会抛出异常,而这一异常会直接影响到发布者,使得发布者程序中止,而后面订阅者的方法将不会被执行。因此我们需要加上异常处理,考虑下面一段程序: class Program5 { static void Main(string[] args) { Publisher pub = new Publisher(); Subscriber1 sub1 = new Subscriber1(); Subscriber2 sub2 = new Subscriber2(); Subscriber3 sub3 = new Subscriber3(); pub.NumberChanged += new DemoEventHandler(sub1.OnNumberChanged); pub.NumberChanged += new DemoEventHandler(sub2.OnNumberChanged); pub.NumberChanged += new DemoEventHandler(sub3.OnNumberChanged); } } public class Publisher { public event EventHandler MyEvent; public void DoSomething() { // 做某些其他的事情 if (MyEvent != null) { try { MyEvent(this, EventArgs.Empty); } catch (Exception e) { Console.WriteLine("Exception: {0}", e.Message); } } } } public class Subscriber1 { public void OnEvent(object sender, EventArgs e) { Console.WriteLine("Subscriber1 Invoked!"); } } public class Subscriber2 { public void OnEvent(object sender, EventArgs e) { throw new Exception("Subscriber2 Failed"); } } public class Subscriber3 {/* 与Subsciber1类同,略*/} 注意到我们在Subscriber2中抛出了异常,同时我们在Publisher中使用了try/catch语句来处理异常。运行上面的代码,我们得到的结果是: 复制代码 代码如下:
可以看到,尽管我们捕获了异常,使得程序没有异常结束,但是却影响到了后面的订阅者,因为Subscriber3也订阅了事件,但是却没有收到事件通知(它的方法没有被调用)。此时,我们可以采用上面的办法,先获得委托链表,然后在遍历链表的循环中处理异常,我们只需要修改一下DoSomething方法就可以了: public void DoSomething() { if (MyEvent != null) { Delegate[] delArray = MyEvent.GetInvocationList(); foreach (Delegate del in delArray) { EventHandler method = (EventHandler)del; // 强制转换为具体的委托类型 try { method(this, EventArgs.Empty); } catch (Exception e) { Console.WriteLine("Exception: {0}", e.Message); } } } } 注意到Delegate是EventHandler的基类,所以为了触发事件,先要进行一个向下的强制转换,之后才能在其上触发事件,调用所有注册对象的方法。除了使用这种方式以外,还有一种更灵活方式可以调用方法,它是定义在Delegate基类中的DynamicInvoke()方法: public object DynamicInvoke(params object[] args); 这可能是调用委托最通用的方法了,适用于所有类型的委托。它接受的参数为object[],也就是说它可以将任意数量的任意类型作为参数,并返回单个object对象。上面的DoSomething()方法也可以改写成下面这种通用形式: public void DoSomething() { // 做某些其他的事情 if (MyEvent != null) { Delegate[] delArray = MyEvent.GetInvocationList(); foreach (Delegate del in delArray) { try { // 使用DynamicInvoke方法触发事件 del.DynamicInvoke(this, EventArgs.Empty); } catch (Exception e) { Console.WriteLine("Exception: {0}", e.Message); } } } } 注意现在在DoSomething()方法中,我们取消了向具体委托类型的向下转换,现在没有了任何的基于特定委托类型的代码,而DynamicInvoke又可以接受任何类型的参数,且返回一个object对象。所以我们完全可以将DoSomething()方法抽象出来,使它成为一个公共方法,然后供其他类来调用,我们将这个方法声明为静态的,然后定义在Program类中: // 触发某个事件,以列表形式返回所有方法的返回值 public static object[] FireEvent(Delegate del, params object[] args){ List<object> objList = new List<object>(); if (del != null) { Delegate[] delArray = del.GetInvocationList(); foreach (Delegate method in delArray) { try { // 使用DynamicInvoke方法触发事件 object obj = method.DynamicInvoke(args); if (obj != null) objList.Add(obj); } catch { } } } return objList.ToArray(); } 随后,我们在DoSomething()中只要简单的调用一下这个方法就可以了: public void DoSomething() { // 做某些其他的事情 Program5.FireEvent(MyEvent, this, EventArgs.Empty); } 注意FireEvent()方法还可以返回一个object[]数组,这个数组包括了所有订阅者方法的返回值。而在上面的例子中,我没有演示如何获取并使用这个数组,为了节省篇幅,这里也不再赘述了,在本文附带的代码中,有关于这部分的演示,有兴趣的朋友可以下载下来看看。 委托中订阅者方法超时的处理 订阅者除了可以通过异常的方式来影响发布者以外,还可以通过另一种方式:超时。一般说超时,指的是方法的执行超过某个指定的时间,而这里我将含义扩展了一下,凡是方法执行的时间比较长,我就认为它超时了,这个“比较长”是一个比较模糊的概念,2秒、3秒、5秒都可以视为超时。超时和异常的区别就是超时并不会影响事件的正确触发和程序的正常运行,却会导致事件触发后需要很长才能够结束。在依次执行订阅者的方法这段期间内,客户端程序会被中断,什么也不能做。因为当执行订阅者方法时(通过委托,相当于依次调用所有注册了的方法),当前线程会转去执行方法中的代码,调用方法的客户端会被中断,只有当方法执行完毕并返回时,控制权才会回到客户端,从而继续执行下面的代码。我们来看一下下面一个例子: (编辑:焦作站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |