深入浅出C#委托与事件系列(六)
获得多个返回值与异常处理 现在假设我们想要获得多个订阅者的返回值,以List<string>的形式返回,该如何做呢?我们应该记得委托定义在编译时会生成一个继承自MulticastDelegate的类,而这个MulticastDelegate又继承自Delegate,在Delegate内部,维护了一个委托链表,链表上的每一个元素,为一个只包含一个目标方法的委托对象。而通过Delegate基类的GetInvocationList()静态方法,可以获得这个委托链表。随后我们遍历这个链表,通过链表中的每个委托对象来调用方法,这样就可以分别获得每个方法的返回值: class Program4 { static void Main(string[] args) { Publishser pub = new Publishser(); 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); List<string> list = pub.DoSomething(); //调用方法,在方法内触发事件 foreach (string str in list) { Console.WriteLine(str); } } } public delegate string DemoEventHandler(int num); // 定义事件发布者 public class Publishser { public event DemoEventHandler NumberChanged; // 声明一个事件 public List<string> DoSomething() { // 做某些其他的事 List<string> strList = new List<string>(); if (NumberChanged == null) return strList; // 获得委托数组 Delegate[] delArray = NumberChanged.GetInvocationList(); foreach (Delegate del in delArray) { // 进行一个向下转换 DemoEventHandler method = (DemoEventHandler)del; strList.Add(method(100)); // 调用方法并获取返回值 } return strList; } } // 定义事件订阅者 public class Subscriber1 { public string OnNumberChanged(int num) { Console.WriteLine("Subscriber1 invoked, number:{0}", num); return "[Subscriber1 returned]"; } } public class Subscriber3 {与上面类同,略} public class Subscriber3 {与上面类同,略} 如果运行上面的代码,可以得到这样的输出: Subscriber1 invoked, number:100 Subscriber2 invoked, number:100 Subscriber3 invoked, number:100 [Subscriber1 returned] [Subscriber2 returned] [Subscriber3 returned] 可见我们获得了三个方法的返回值。而我们前面说过,很多情况下委托的定义都不包含返回值,所以上面介绍的方法似乎没有什么实际意义。其实通过这种方式来触发事件最常见的情况应该是在异常处理中,因为很有可能在触发事件时,订阅者的方法会抛出异常,而这一异常会直接影响到发布者,使得发布者程序中止,而后面订阅者的方法将不会被执行。因此我们需要加上异常处理,考虑下面一段程序: 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语句来处理异常。运行上面的代码,我们得到的结果是: Subscriber1 Invoked! Exception: Subscriber2 Failed 可以看到,尽管我们捕获了异常,使得程序没有异常结束,但是却影响到了后面的订阅者,因为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[]数组,这个数组包括了所有订阅者方法的返回值。而在上面的例子中,我没有演示如何获取并使用这个数组,为了节省篇幅,
这里也不再赘述了,在本文附带的代码中,有关于这部分的演示,有兴趣的朋友可以下载下来看看。
============ 欢迎各位老板打赏~ ===========
与本文相关的文章
- · 深入浅出C#委托与事件系列(八)
- · 深入浅出C#委托与事件系列(七)
- · 深入浅出C#委托与事件系列(五)
- · 深入浅出C#委托与事件系列(四)
- · 深入浅出C#委托与事件系列(三)
- · 深入浅出C#委托与事件系列(二)
- · 深入浅出C#委托与事件系列(一)
- · The instance of entity type ‘Customer’ cannot be tracked because another instance with the same key value for {‘Id’} is already being tracked.
- · .NET8实时更新nginx ip地址归属地
- · 解决.NET Blazor子组件不刷新问题
- · .NET8如何在普通类库中引用 Microsoft.AspNetCore
- · .NET8 Mysql SSL error