当委托有返回值,事件注册了多个方法后,触发事件,到底是哪个注册方法返回值呢?
模拟"我是歌手"采访观众,把"我是歌手"看作被监视对象,把观众看作是观察者Observer。
让最后一个注册方法返回值
1: namespace ConsoleApplication15
2: {
3: class Program
4: {
5: static void Main(string[] args)
6: {
7: ImSinger singer = new ImSinger();
8: Audience1 a1 = new Audience1();
9: Audience2 a2 = new Audience2();
10: Audience3 a3 = new Audience3();
11:
12: singer.CommentChanged += new CommentEventHandler(a1.OnCommentChanged);
13: singer.CommentChanged += new CommentEventHandler(a2.OnCommentChanged);
14: singer.CommentChanged += new CommentEventHandler(a3.OnCommentChanged);
15:
16: Console.WriteLine("主持人:您觉得本场我是歌手谁的表现最好?");
17: singer.Interview();
18: Console.ReadKey();
19: }
20: }
21:
22: //定义委托
23: public delegate string CommentEventHandler();
24:
25: public class ImSinger
26: {
27: public event CommentEventHandler CommentChanged;
28:
29: public void Interview()
30: {
31: if (CommentChanged != null)
32: {
33: string rtn = CommentChanged();
34: Console.WriteLine(rtn);
35: }
36: }
37: }
38:
39: public class Audience1
40: {
41: public string OnCommentChanged()
42: {
43: return "观众1:我觉得周笔畅发挥最好!";
44: }
45: }
46:
47: public class Audience2
48: {
49: public string OnCommentChanged()
50: {
51: return "观众2:我觉得张杰发挥最好!";
52: }
53: }
54: public class Audience3
55: {
56: public string OnCommentChanged()
57: {
58: return "观众3:我觉得邓紫棋发挥最好!";
59: }
60: }
61: }
62:
结果:
可见,最后一个注册方法返回了值。如果导演想任意控制哪个注册方法起效呢?
让任意注册方法返回值
● 可以设计一个"事件访问器Event Accessor",可以像属性一样给委托变量赋值,或读取委托变量的值。
● 而且还要设计一个私有的委托变量来配合事件访问器
1: private CommentEventHandler commentChanged;
2: public event CommentEventHandler CommentChanged
3: {
4: add { commentChanged = value; }
5: remove { commentChanged -= value; }
6: }
完整如下:
1: namespace ConsoleApplication15
2: {
3: class Program
4: {
5: static void Main(string[] args)
6: {
7: ImSinger singer = new ImSinger();
8: Audience1 a1 = new Audience1();
9: Audience2 a2 = new Audience2();
10: Audience3 a3 = new Audience3();
11:
12: singer.CommentChanged -= a1.OnCommentChanged;
13: singer.CommentChanged += a2.OnCommentChanged;
14: singer.CommentChanged += a1.OnCommentChanged;
15:
16: Console.WriteLine("主持人:您觉得本场我是歌手谁的表现最好?");
17: singer.Interview();
18: Console.ReadKey();
19: }
20: }
21:
22: //定义委托
23: public delegate string CommentEventHandler();
24:
25: public class ImSinger
26: {
27: private CommentEventHandler commentChanged;
28: public event CommentEventHandler CommentChanged
29: {
30: add { commentChanged = value; }
31: remove { commentChanged -= value; }
32: }
33:
34: public void Interview()
35: {
36: if (commentChanged != null)
37: {
38: string rtn = commentChanged();
39: Console.WriteLine(rtn);
40: }
41: }
42: }
43:
44: public class Audience1
45: {
46: public string OnCommentChanged()
47: {
48: return "观众1:我觉得周笔畅发挥最好!";
49: }
50: }
51:
52: public class Audience2
53: {
54: public string OnCommentChanged()
55: {
56: return "观众2:我觉得张杰发挥最好!";
57: }
58: }
59: public class Audience3
60: {
61: public string OnCommentChanged()
62: {
63: return "观众3:我觉得邓紫棋发挥最好!";
64: }
65: }
66: }
67:
可见,通过事件访问器可以任意注册和注销方法。
获取多个注册方法的多个返回值
委托的基类Delegate维护了一个委托链表,每行存储着一个注册方法,可以通过基类的GetInvocationList()获得这个委托链表。然后遍历委托链表,进行向下转换成自定义委托,执行方法。
1: namespace ConsoleApplication15
2: {
3: class Program
4: {
5: static void Main(string[] args)
6: {
7: ImSinger singer = new ImSinger();
8: Audience1 a1 = new Audience1();
9: Audience2 a2 = new Audience2();
10: Audience3 a3 = new Audience3();
11:
12: singer.CommentChanged += new CommentEventHandler(a1.OnCommentChanged);
13: singer.CommentChanged += new CommentEventHandler(a2.OnCommentChanged);
14: singer.CommentChanged += new CommentEventHandler(a3.OnCommentChanged);
15:
16: Console.WriteLine("主持人:您觉得本场我是歌手谁的表现最好?");
17: List<string> list = singer.Interview();
18: foreach (string s in list)
19: {
20: Console.WriteLine(s);
21: }
22: Console.ReadKey();
23: }
24: }
25:
26: //定义委托
27: public delegate string CommentEventHandler();
28:
29: public class ImSinger
30: {
31: public event CommentEventHandler CommentChanged;
32:
33: public List<string> Interview()
34: {
35: List<string> strList = new List<string>();
36: if (CommentChanged == null) return strList;
37:
38: Delegate[] delArray = CommentChanged.GetInvocationList();
39: foreach (Delegate del in delArray)
40: {
41: CommentEventHandler method = (CommentEventHandler) del; //向下转换
42: strList.Add(method());
43: }
44: return strList;
45: }
46: }
47:
48: public class Audience1
49: {
50: public string OnCommentChanged()
51: {
52: return "观众1:我觉得周笔畅发挥最好!";
53: }
54: }
55:
56: public class Audience2
57: {
58: public string OnCommentChanged()
59: {
60: return "观众2:我觉得张杰发挥最好!";
61: }
62: }
63: public class Audience3
64: {
65: public string OnCommentChanged()
66: {
67: return "观众3:我觉得邓紫棋发挥最好!";
68: }
69: }
70: }
71:
订阅者方法出现异常如何处理
What:
订阅者方法可能会抛出异常。
Why:
会使发布者程序终止,而出现异常的订阅者后面的订阅者方法将不会被执行。
how:
我们要针对订阅者方法可能出现的异常,进行异常处理。
□ 问题呈现:让一个订阅者方法出现异常,后续注册方法不能被执行
1: namespace ConsoleApplication15
2: {
3: class Program
4: {
5: static void Main(string[] args)
6: {
7: ImSinger singer = new ImSinger();
8: Audience1 a1 = new Audience1();
9: Audience2 a2 = new Audience2();
10: Audience3 a3 = new Audience3();
11:
12: singer.CommentChanged += new CommentEventHandler(a1.OnCommentChanged);
13: singer.CommentChanged += new CommentEventHandler(a2.OnCommentChanged);
14: singer.CommentChanged += new CommentEventHandler(a3.OnCommentChanged);
15:
16: Console.WriteLine("主持人:您觉得本场我是歌手谁的表现最好?");
17: singer.Interview();
18: Console.ReadKey();
19: }
20: }
21:
22: //定义委托
23: public delegate void CommentEventHandler();
24:
25: public class ImSinger
26: {
27: public event CommentEventHandler CommentChanged;
28:
29: public void Interview()
30: {
31: if (CommentChanged != null)
32: {
33: try
34: {
35: CommentChanged();
36: }
37: catch (Exception e)
38: {
39:
40: Console.WriteLine("有订阅者方法出现异常了:" +e.Message);
41: }
42: }
43: }
44: }
45:
46: public class Audience1
47: {
48: public void OnCommentChanged()
49: {
50: Console.WriteLine("观众1:我觉得周笔畅发挥最好!");
51: }
52: }
53:
54: public class Audience2
55: {
56: public void OnCommentChanged()
57: {
58: throw new Exception("我是2号观众,对不起,我卡了");
59: }
60: }
61: public class Audience3
62: {
63: public void OnCommentChanged()
64: {
65: Console.WriteLine("观众3:我觉得邓紫棋发挥最好!");
66: }
67: }
68: }
69:
可见,订阅者Audience2的方法出现异常,导致后面的订阅者Audience3方法无法被执行。
□ 在委托链表中处理异常,一个订阅者方法出现异常,后续注册方法依然能被执行
what:
在委托链中处理异常,一旦订阅者的注册方法抛异常,也不会影响后续订阅者的注册方法。
why:
因为要遍历委托链中的某个委托,即注册方法,当一个注册方法抛异常后,还会遍历后续的注册方法。
1: namespace ConsoleApplication15
2: {
3: class Program
4: {
5: static void Main(string[] args)
6: {
7: ImSinger singer = new ImSinger();
8: Audience1 a1 = new Audience1();
9: Audience2 a2 = new Audience2();
10: Audience3 a3 = new Audience3();
11:
12: singer.CommentChanged += new CommentEventHandler(a1.OnCommentChanged);
13: singer.CommentChanged += new CommentEventHandler(a2.OnCommentChanged);
14: singer.CommentChanged += new CommentEventHandler(a3.OnCommentChanged);
15:
16: Console.WriteLine("主持人:您觉得本场我是歌手谁的表现最好?");
17: singer.Interview();
18: Console.ReadKey();
19: }
20: }
21:
22: //定义委托
23: public delegate void CommentEventHandler();
24:
25: public class ImSinger
26: {
27: public event CommentEventHandler CommentChanged;
28:
29: public void Interview()
30: {
31: if (CommentChanged != null)
32: {
33: Delegate[] delArray = CommentChanged.GetInvocationList();
34: foreach (Delegate del in delArray)
35: {
36: CommentEventHandler method = (CommentEventHandler)del;
37: try
38: {
39: method();
40: }
41: catch (Exception e)
42: {
43: Console.WriteLine("订阅者方法有异常:" + e.Message);
44: }
45: }
46: }
47: }
48: }
49:
50: public class Audience1
51: {
52: public void OnCommentChanged()
53: {
54: Console.WriteLine("观众1:我觉得周笔畅发挥最好!");
55: }
56: }
57:
58: public class Audience2
59: {
60: public void OnCommentChanged()
61: {
62: throw new Exception("我是2号观众,对不起,我卡了");
63: }
64: }
65: public class Audience3
66: {
67: public void OnCommentChanged()
68: {
69: Console.WriteLine("观众3:我觉得邓紫棋发挥最好!");
70: }
71: }
72: }
73:
改进:
CommentEventHandler method = (CommentEventHandler)del;这里有一个强制向下转换,可以用DynamicInvoke()方法替代。
1: //定义委托
2: public delegate void CommentEventHandler();
3:
4: public class ImSinger
5: {
6: public event CommentEventHandler CommentChanged;
7:
8: public void Interview()
9: {
10: if (CommentChanged != null)
11: {
12: Delegate[] delArray = CommentChanged.GetInvocationList();
13: foreach (Delegate del in delArray)
14: {
15: try
16: {
17: del.DynamicInvoke();
18: }
19: catch (Exception e)
20: {
21: Console.WriteLine("订阅者方法有异常:" + e.Message);
22: }
23: }
24: }
25: }
26: }
27:
但使用DynamicInvoke()不好的地方在于:
一旦订阅者方法抛异常,程序就中断了,并抛出异常。
参考资料:
※ 《.NET之美》--张子阳,感谢!