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类,事件是“小明”实例