C# 委托/事件本质详解

委托

一.什么是委托

IL层面
1>委托的本质就是一个类
2>继承自System.MulticastDelegate
3>委托里面内置了3个方法:Invoke(),BeginInvoke(),EndInvoke()

 

二.委托的三个步骤
1>public delegate void MyDelegate();//1.委托的声明
2>MyDelegate myDelegate = new MyDelegate(DoSomething);//2.委托的实例化(构造函数传方法)
3>myDelegate.Invoke();//3.实例调用(Invok调用),这里等于执行了这个方法
myDelegate();//3.直接调用也和上面一行代码是一样的

 

三.委托的3大意义
1>解耦:
-好处是减少重复代码;
-解耦是解除了判断逻辑和共用逻辑之间的耦合;
-委托传递的是逻辑(逻辑就是方法);
2>异步多线程
3>多播委托
-+=为委托实例按顺序增加方法,形成方法链,Invok()时按顺序执行
--=为委托实例移除方法,从方法链的尾部开始匹配,遇到第一个完全吻合的移除,且只移除一个;没有的也不异常
-多播委托带返回值,结果以最后的为准
-价值:一个变量保存多个方法,可以增减方法;Invok时候可顺序执行

 多播委托

啥叫做多播委托呢,说白了就是像广播一样的将消息传播到四面八方。多播委托就是一个方法链,Invoke时按照方法链顺序执行。多播委托就像糖葫芦串,执行Invoke的时候,就像吃糖葫芦串一样,从上到下的一个个的吃。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.ComponentModel;
 4 using System.Data;
 5 using System.Drawing;
 6 using System.Linq;
 7 using System.Text;
 8 using System.Threading.Tasks;
 9 using System.Windows.Forms;
10 
11 namespace 事件
12 {
13     public partial class Form1 : Form
14     {
15         public delegate void Mydelegate();
16         public Form1()
17         {
18             InitializeComponent();
19         }
20         /// <summary>
21         /// 多播委托,可以理解为一个变量保存多个方法,可增减;Invoke的时候顺序执行
22         /// +=为委托实例按照顺序增加方法,形成方法链,Invoke时顺序执行
23         /// </summary>
24         /// <param name="sender"></param>
25         /// <param name="e"></param>
26         private void button1_Click(object sender, EventArgs e)
27         {
28             Mydelegate mydele = new Mydelegate(WriteInfo1);
29             mydele += new Mydelegate(WriteInfo2);
30             mydele += new Mydelegate(WriteInfo3);
31             mydele.Invoke();
32         }
33         public void WriteInfo1()
34         {
35             Console.WriteLine("1");
36         }
37         public void WriteInfo2()
38         {
39             Console.WriteLine("2");
40         }
41         public void WriteInfo3()
42         {
43             Console.WriteLine("3");
44         }
45     }
46 }

当下面的代码

 1      /// <summary>
 2         /// 多播委托,可以理解为一个变量保存多个方法,可增减;Invoke的时候顺序执行
 3         /// +=为委托实例按照顺序增加方法,形成方法链,Invoke时顺序执行
 4         /// -=为委托移除方法,从方法的尾部开始匹配,遇到第一个完全吻合的,移除且移除一个;就算方法链中没有
 5         /// 这个方法,移除的时候也不会报错。
 6         /// </summary>
 7         /// <param name="sender"></param>
 8         /// <param name="e"></param>
 9         private void button1_Click(object sender, EventArgs e)
10         {
11             Mydelegate mydele = new Mydelegate(WriteInfo1);
12             //mydele += new Mydelegate(WriteInfo2);
13             mydele += new Mydelegate(WriteInfo3);
14             mydele -= new Mydelegate(WriteInfo2);
15             mydele.Invoke();
16         }

 

(容易入坑)单例中,事件重复注册

执行结果

 

 1 using System;
 2 using System.Collections.Generic;
 3 using System.ComponentModel;
 4 using System.Data;
 5 using System.Drawing;
 6 using System.Linq;
 7 using System.Text;
 8 using System.Threading.Tasks;
 9 using System.Windows.Forms;
10 
11 namespace 事件
12 {
13     public class Cat
14     {
15         public delegate void Mydelegate();
16         public event Mydelegate MyEvent;
17         public void MiaoMiao()
18         {
19             MyEvent?.Invoke();
20         }
21     }
22     public partial class Form1 : Form
23     {
24         int i=0;
25         Cat cat;
26         public Form1()
27         {
28             InitializeComponent();
29         }
30 
31         private void button1_Click(object sender, EventArgs e)
32         {
33             i++;
34             cat = new Cat();
35             cat.MyEvent += WriteInfo1;
36             cat.MyEvent += WriteInfo2;
37             cat.MyEvent += WriteInfo3;
38             cat.MiaoMiao();
39         }
40         public void WriteInfo1()
41         {
42             Console.WriteLine("1");
43         }
44         public void WriteInfo2()
45         {
46             Console.WriteLine("2");
47         }
48         public void WriteInfo3()
49         {
50             Console.WriteLine("3");
51         }
52     }
53 }

上面代码和下面代码实质是一样的。

 

 下面的代码执行3遍,执行结果如上图。这个之前在工作中遇到的一个问题,困扰了半天才解决。说白了,就是单例被重复注册事件了。解决办法,就是每次调用

RemoveAllEvent移除单例中注册的事件

 1 using System;
 2 using System.Collections.Generic;
 3 using System.ComponentModel;
 4 using System.Data;
 5 using System.Drawing;
 6 using System.Linq;
 7 using System.Reflection;
 8 using System.Text;
 9 using System.Threading.Tasks;
10 using System.Windows.Forms;
11 
12 namespace 事件
13 {
14     public class Cat
15     {
16         public delegate void Mydelegate();
17         public event Mydelegate MyEvent;
18         public void MiaoMiao()
19         {
20             MyEvent?.Invoke();
21         }
22     }
23     public partial class Form1 : Form
24     {
25         int i = 0;
26         Cat cat = new Cat();
27         public Form1()
28         {
29             InitializeComponent();
30         }
31 
32         private void button1_Click(object sender, EventArgs e)
33         {
34             //RemoveAllEvent(cat);
35             i++;
36             cat.MyEvent += WriteInfo1;
37             cat.MiaoMiao();
38         }
39         public void WriteInfo1()
40         {
41             Console.WriteLine(i);
42         }
43         /// <summary>
44         /// 移除所有注册事件
45         /// </summary>
46         public void RemoveAllEvent(Cat cats)
47         {
48             var newType = cats.GetType();
49             foreach (var item in newType.GetEvents())
50             {
51                 FieldInfo field = newType.GetField(item.Name, BindingFlags.Instance | BindingFlags.NonPublic);
52                 if(field !=null)
53                 {
54                     object fileValue = field.GetValue(cats);
55                     if(fileValue !=null && fileValue is Delegate)
56                     {
57                         Delegate objectDele = (Delegate)fileValue;
58                         Delegate[] involkList = objectDele.GetInvocationList();
59                         if(involkList !=null)
60                         {
61                             foreach(Delegate del in involkList)
62                             {
63                                 item.RemoveEventHandler(cats, del);
64                             }
65                         }
66                     }
67                 }
68             }
69         }
70     }
71 }

 

静态事件,不同实例中重复了订阅方法

执行两次button1_Click方法,结果如下图。其中第二次执行的时候,发现输出结果都重复了。

 原因分析:

 

 

 解决办法:

直接把Global.MyEvent赋值为null,它之前的实例就无引用了,GC就会检测到并回收

 

 

 1 using System;
 2 using System.Collections.Generic;
 3 using System.ComponentModel;
 4 using System.Data;
 5 using System.Drawing;
 6 using System.Linq;
 7 using System.Text;
 8 using System.Threading.Tasks;
 9 using System.Windows.Forms;
10 
11 namespace _23_测试专用
12 {
13     public class Global
14     {
15         public static Action<string> MyEvent;
16         public static void FireTaskLog(string log)
17         {
18             MyEvent?.Invoke(log);
19         }
20     }
21     public class MainTest
22     {
23         public MainTest()
24         {
25             Global.MyEvent += OnMyEvent;
26         }
27 
28         private void OnMyEvent(string log)
29         {
30             Console.WriteLine(log);
31         }
32         public void Start()
33         {
34             for (int i = 0; i < 2; i++)
35             {
36                 Global.FireTaskLog((i+1).ToString());
37             }
38         }
39     }
40     public partial class Form1 : Form
41     {
42         MainTest mainTest;
43         public Form1()
44         {
45             InitializeComponent();
46         }
47 
48         private void button1_Click(object sender, EventArgs e)
49         {
50             mainTest = new MainTest();
51             mainTest.Start();
52         }
53     }
54 }

 

事件

一.什么是事件
1>说白了,就是带event关键字的委托实例
2>事件可以限制变量外部调用,或者直接赋值
3>事件可以把一堆的动作或行为,封装出去,交给第三方指定
4>程序设计时候:
-固定部分,可以写死
-不固定部分,通过一个事件去开放接口,外部可以扩展动作

 

二.委托和事件的区别和联系
1>委托是一个类型,一个类,事件是委托的实例
2>比如委托时一个Student类,事件是“小明”实例

 

posted @ 2020-06-11 16:29  东方承丘  阅读(1202)  评论(0编辑  收藏  举报