C# 委托 事件
一:什么叫委托
通过反射发现,委托其实是一个类,继承自System.MulticastDelegate,但是System.MulticastDelegate这个类是特殊类,不能被继承
二:委托的声明
public delegate void NoReturnNoParaOutClass(); public class MyDelete { public delegate void NoReturnNoPara<T>(T t); public delegate void NoReturnNoPara(); public delegate void NoReturnWithPara(int x, int y); public delegate int WithReturnNoPara(); public delegate string WithReturnWithPara(out int x, ref int y); }
委托可以声明在类外面,可以声明再类里面
三:委托的实例和调用
private int GetSomething() { return 1; } private int GetSomething2() { return 2; } private int GetSomething3() { return 3; } private void DoNothing() { Console.WriteLine("This is DoNothing"); } private static void DoNothingStatic() { Console.WriteLine("This is DoNothingStatic"); } public string ParaReturn(out int x, ref int y) { throw new Exception(); }
//多种途径实例化,要求传递一个参数类型,返回值都跟委托一致的方法 { WithReturnWithPara method = new WithReturnWithPara(ParaReturn); int x = 0; int y = 0; var dd = method.Invoke(out x, ref y); } //begininvoke { WithReturnNoPara method = new WithReturnNoPara(this.GetSomething); int iResult = method.Invoke(); iResult = method(); var result = method.BeginInvoke(null, null);//异步调用 method.EndInvoke(result); } { NoReturnNoPara method = new NoReturnNoPara(this.DoNothing); //委托实力的调用,参数和委托约束的一致 method.Invoke(); //1 //method(); //2 //method.BeginInvoke(null, null); //3 //this.DoNothing(); //1,2,3都等同于this.DoNothing } { NoReturnNoPara method = new NoReturnNoPara(DoNothingStatic); } { NoReturnNoPara method = new NoReturnNoPara(Student.StudyAdvanced); } { NoReturnNoPara method = new NoReturnNoPara(new Student().Study); }
四:为什么要使用委托
有时候我们声明一个方法,直接调用蛮好的,为啥还要使用委托,然后还要先声明,再实例化,再inovke调用呢?
下面我们举个例子,比如一个人问好这件事情,不同人问候方式不一样,我们会先定义一个类型,如枚举
public enum PeopleType { Chinese, America, Japanese }
然后通过不同的类型来判断问候方式不同,如下
/// 为不同的人,进行不同的问候 /// 传递变量--判断一下----执行对应的逻辑 /// </summary> /// <param name="name"></param> /// <param name="peopleType"></param> public void SayHi(string name, PeopleType peopleType) { switch (peopleType) { case PeopleType.Chinese: Console.WriteLine($"{name}晚上好"); break; case PeopleType.America: Console.WriteLine($"{name},good evening"); break; case PeopleType.Japanese: Console.WriteLine($"{name},&&%*^^***@@@&&&&"); break; default: throw new Exception("wrong peopleType"); //遇到异常报错 } }
这样做的好处是:以后如果增加公共逻辑等比较容易,但是如果类型比较多,这个方法会变成无限制改动,导致方法难以维护,于是很多人想着增加分支,就增加方法--不影响别的方法的思路来改善
public void SayHiChinese(string name) { Console.WriteLine($"{name}晚上好"); } public void SayHiJapanese(string name) { Console.WriteLine($"{name},&&%*^^***@@@&&&&"); } public void SayHiAmerican(string name) { Console.WriteLine($"{name},good evening"); }
然后上层判断调用
这样做的好处是:修改某个方法--不影响别的方法 ,但是缺点却是:增加公共逻辑---多个方法就有很多重复代码
那么我们想:既增加逻辑方便,又维护简单,鱼肉熊掌,如何兼得呢?
我们可以把相应的逻辑做为参数传进来,这样就解决了我们的问题
具体我们可以按照以下来做:
public void SayHiPerfact(string name, SayHiDeletegate method) { Console.WriteLine("增加开始日志"); method.Invoke(name); Console.WriteLine("增加结束日志"); } public delegate void SayHiDeletegate(string name);
然后调用的时候如下:
SayHiDeletegate method = new SayHiDeletegate(SayHiChinese);
这样就做到了
1:逻辑解耦,方便维护
2:代码重构,去掉重复
其实这也是我们选择使用委托的两大优点
注意:以上我们纯粹为了定义委托而定义委托,其实框架已经我们帮我们定义了Action 和Func这两个委托,Action是没有返回值,Func是有返回值的,这两个委托类已经足够我们使用了,所以有时候我们使用的时候,没有必要自己再去定义,而直接使用即可
1:Action: 系统提供的,0-16个泛型参数,不带返回值的 委托
//Action 系统提供的,0-16个泛型参数,不带返回值的 委托 Action action0 = new Action(DoNothing); Action action1 = this.DoNothing; //语法糖,就是编译器帮我们添加上new Action Action<int> action2 = this.ShowInt;
2:Func 系统提供的,0-16个泛型参数,带一个返回值的 委托
//Func 系统提供的,0-16个泛型参数,带返回值的 委托 Func<int> func = this.GetSomething2; func.Invoke(); Func<int, string> func1 = this.ToString; func1.Invoke(1);
3:系统或者框架为什么要封装这样的方法?第一步我们定义一个这样的方法,需要传入一个Action
private void DoAction(Action act) { act.Invoke(); }
然后我们可以这样调用:
Action action0 = this.DoNothing; NoReturnNoPara method = this.DoNothing; // this.DoAction(method); //这样就不行,因为类型不一致 this.DoAction(action0);
但是我们不能使用 this.DoAction(method),因为:委托的本质是类,action和NoReturnNoPara是不同的类,虽然实例化都可以传递相同的方法,但是没有父子关系,所以不能替换,就像student和teacher两个类,实例化都是传递id/name,但是二者不能替换
所以:框架提供这种封装,自然是希望大家都统一使用Action/Func,但是之前还有很多委托,至今再框架中还能看到,因为.net向前兼容,以前的版本去不掉的,保留着,这是历史包袱,此后我们就不要定义新的委托了!
五:多播委托
委托是一个类,然后继承于MulticastDelegate(多播委托),但是这个类是特殊类,所以任何一个委托都是多播委托类型的子类
1 { 2 //多播委托:任何一个委托都是多播委托类型的子类,可以通过+=去添加方法 3 //多播委托如果中间出现未捕获的异常,方法链直接结束 4 //多播委托:一个变量保存多个方法,可以增减;invoke的时候可以按顺序执行 5 //+= 为委托实例按顺序增加方法,形成方法链,Invoke时,按顺序依次执行系列方法 6 Student studentNew = new Student(); 7 8 NoReturnNoPara method = new NoReturnNoPara(this.DoNothing); //普通的DoNothing方法 9 method += new NoReturnNoPara(this.DoNothing); //普通的DoNothing方法 10 method += new NoReturnNoPara(DoNothingStatic); //静态方法 11 method += new NoReturnNoPara(Student.StudyAdvanced); //静态方法 12 method += new NoReturnNoPara(new Student().Study);//不是同一个实例,所以是不同的方法 13 method += new NoReturnNoPara(studentNew.Study); 14 method.Invoke(); 15 //method.BeginInvoke(null, null);//委托里面如果有多个Target,则不能异步调用,多播委托是不能异步的 16 17 foreach (Action item in method.GetInvocationList()) //得到委托实例的target方法 ,静态方法target为null 18 { 19 item.Invoke(); 20 //item.BeginInvoke(null, null); 21 } 22 23 //-= 为委托实例移除方法,从方法链的尾部开始匹配,遇到第一个完全吻合的,移除且只移除一个,没有也不异常 24 method -= new NoReturnNoPara(this.DoNothing); 25 method -= new NoReturnNoPara(DoNothingStatic); 26 method -= new NoReturnNoPara(Student.StudyAdvanced); 27 method -= new NoReturnNoPara(new Student().Study); //去不掉,原因是不同的实例的相同方法,并不吻合 28 method -= new NoReturnNoPara(studentNew.Study); 29 method.Invoke(); 30 } 31 { 32 WithReturnNoPara method = new WithReturnNoPara(this.GetSomething); 33 method += new WithReturnNoPara(this.GetSomething2); 34 method += new WithReturnNoPara(this.GetSomething3); 35 int iResult = method.Invoke();//多播委托带返回值,结果以最后的为准,所以一般多播委托用的是不带返回值的 36 }
注意:
+= 为委托实例按顺序增加方法,形成方法链,Invoke时,按顺序依次执行系列方法
-= 为委托实例移除方法,从方法链的尾部开始匹配,遇到第一个完全吻合(new Student().Study如果声明两次则不属于吻合)的,移除且只移除一个,没有也不异常
多播委托其实就是观察者模式的缩影。
比如一只猫叫了一声,然后需要触发一系列的动作,比如老鼠跑,孩子哭,狗叫等等
如果我们把这些都写在猫的miao的方法中,如下:
public void Miao() { Console.WriteLine("{0} Miao", this.GetType().Name); new Mouse().Run(); new Baby().Cry(); new Mother().Wispher(); //new Brother().Turn(); new Father().Roar(); new Neighbor().Awake(); new Stealer().Hide(); new Dog().Wang(); }
上面的代码:
依赖太重,依赖多个类型,任何类型的变化都得修改猫
职责耦合,猫不仅自己miao,还得找各种对象执行各种动作甚至控制顺序
任意环节增加或者减少调整顺序, 都得修改猫
那么我们可以修改代码,然后Cat的这类中,增加一个委托,然后猫只是自己叫,具体的其他的动作,只需要执行这个多播委托即可,代码如下:
//猫 叫一声 触发一系列后续动作 //多了个 指定动作 正是这个不稳定 封装出去 甩锅 public MiaoDelegate MiaoDelegateHandler; public void MiaoNew() { Console.WriteLine("{0} MiaoNew", this.GetType().Name); if (this.MiaoDelegateHandler != null) { this.MiaoDelegateHandler.Invoke(); } }
然后外面调用的时候可以直接通过下面:
{ Cat cat = new Cat(); cat.MiaoDelegateHandler += new MiaoDelegate(new Mouse().Run); cat.MiaoDelegateHandler += new MiaoDelegate(new Baby().Cry); cat.MiaoDelegateHandler += new MiaoDelegate(new Mother().Wispher); cat.MiaoDelegateHandler += new MiaoDelegate(new Brother().Turn); cat.MiaoDelegateHandler += new MiaoDelegate(new Father().Roar); cat.MiaoDelegateHandler += new MiaoDelegate(new Neighbor().Awake); cat.MiaoDelegateHandler += new MiaoDelegate(new Stealer().Hide); cat.MiaoDelegateHandler += new MiaoDelegate(new Dog().Wang); cat.MiaoNew(); Console.WriteLine("***************************"); }
这样猫就减少了依赖,而且猫叫后,各种动作的顺序也可以随便改变,而不会改变猫叫的方法!
六:事件
事件:是带event关键字的委托的实例,event可以限制变量被外部调用/直接赋值
event:限制权限,只允许在事件声明类里面去invoke和赋值,不允许外面,甚至子类都能运用
event事件只能声明在类中,而委托可以声明在类外面
比如我们在cat类中定义一个事件
public event MiaoDelegate MiaoDelegateHandlerEvent; public void MiaoNewEvent() { Console.WriteLine("{0} MiaoNewEvent", this.GetType().Name); if (this.MiaoDelegateHandlerEvent != null) { this.MiaoDelegateHandlerEvent.Invoke(); } }
然后只能在cat类中中进行invoke调用,如果我们定义一个子类继承cat类,如下:
public class ChildClass : Cat { public void Show() { this.MiaoDelegateHandlerEvent += null; if (this.MiaoDelegateHandlerEvent != null)//子类也不能调用,调用报错 { this.MiaoDelegateHandlerEvent.Invoke(); } } }
this.MiaoDelegateHandlerEvent.Invoke(); 这样是不能使用的,Invoke这个是完全不能调用的!
委托和事件的区别与联系?
委托是一个类型,比如Student
事件是委托类型的一个实例,加上了event的权限控制 比如学生加菲猫,是一个确切的实体
然后事件也可以通过下面来调用多个方法
{ Cat cat = new Cat(); //cat.MiaoDelegateHandler += new MiaoDelegate(new Mouse().Run); cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Baby().Cry); cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Mother().Wispher); cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Brother().Turn); cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Father().Roar); cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Neighbor().Awake); cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Stealer().Hide); cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Dog().Wang); cat.MiaoNewEvent(); Console.WriteLine("***************************"); }
下面写一个标准的的事件,事件一般分为三种:
1:事件的发布者:发布事件,并且在满足条件时候触发事件
2:事件的订户:关注事件,事件发生后,自己做出相应的动作
3:事件的订阅:把订户和发布者的事件关联起来
1 /// <summary> 2 /// 委托是一种类型,静态类不能被继承,所以委托不能声明静态, 3 /// event只是一个实例,所以可以生成实例 4 /// </summary> 5 class EventStandard 6 { 7 /// <summary> 8 /// 订阅:把订户和发布者的事件关联起来 9 /// </summary> 10 public static void Show() 11 { 12 iPhoneX phone = new iPhoneX() 13 { 14 Id = 1234, 15 Tag = "1.0" 16 }; 17 // 订阅:把订户和发布者的事件关联起来 18 phone.DiscountHandler += new Student().Buy; 19 phone.DiscountHandler += new Teacher().Notice; 20 21 phone.Price = 10000; 22 23 } 24 /// <summary> 25 /// 订户:关注事件,事件发生后,自己做出对应的动作 26 /// </summary> 27 public class Student 28 { 29 public void Buy(object sender, EventArgs e) 30 { 31 iPhoneX phone = (iPhoneX)sender; 32 Console.WriteLine($"this is {phone.Tag} iphoneX"); 33 XEventArgs args = (XEventArgs)e; 34 Console.WriteLine($"之前的价格{args.OldPrice}"); 35 Console.WriteLine($"限制的价格{args.NewPrice}"); 36 Console.WriteLine("立马买!!"); 37 } 38 } 39 public class Teacher 40 { 41 public void Notice(object sender, EventArgs e) 42 { 43 iPhoneX phone = (iPhoneX)sender; 44 Console.WriteLine($"this is {phone.Tag} iphoneX"); 45 XEventArgs args = (XEventArgs)e; 46 Console.WriteLine($"之前的价格{args.OldPrice}"); 47 Console.WriteLine($"限制的价格{args.NewPrice}"); 48 Console.WriteLine("立马买!!"); 49 } 50 } 51 52 /// <summary> 53 /// 事件参数 一般会为特定的事件去封装个参数 54 ///订户:Teacher/Student:关注事件,事件发生后,自己做出对应的动作 55 /// </summary> 56 public class XEventArgs : EventArgs 57 { 58 public int OldPrice { set; get; } 59 public int NewPrice { set; get; } 60 } 61 62 63 /// <summary> 64 /// 事件的发布者,发布事件,并且在满足条件时候触发事件 65 /// </summary> 66 public class iPhoneX 67 { 68 public int Id { set; get; } 69 public string Tag { set; get; } 70 public int Price 71 { 72 set 73 { 74 if (value < this._price) 75 { 76 this.DiscountHandler?.Invoke(this, new XEventArgs() { OldPrice = this._price, NewPrice = value }); 77 this._price = value; 78 } 79 } 80 get { return this._price; } 81 } 82 83 private int _price; 84 85 /// <summary> 86 /// 打折事件 87 /// 88 /// </summary> 89 public event EventHandler DiscountHandler; 90 } 91 }
EventHandler:框架自带,表示将用于处理不具有事件数据的事件的方法。EventHandler(object sender, EventArgs e)
下面这个例子也很经典
1 /// <summary> 2 /// 妈妈做好饭,触发爸爸和儿子一起吃饭,并且妈妈会把对应的菜单同时告知爸爸和儿子 3 /// </summary> 4 public class MyEvenStandard 5 { 6 public class Meal 7 { 8 public static void FinishMeal() 9 { 10 Mom mom = new Mom(); 11 mom.EatHandler += (new Dad()).Eat; 12 mom.EatHandler += (new Son()).Eat; 13 mom.Cook(); 14 } 15 } 16 17 public class Mom 18 { 19 //EventHandler(object sender, EventArgs e) 事件的原型,里面sender则是发布者自己,e:是发布者的交互参数 20 public event EventHandler EatHandler; 21 public void Cook() 22 { 23 Console.WriteLine("开始吃饭了"); 24 EatHandler?.Invoke(this, new MenuArags() { Fruid = "orange", Greens = "green pepper", Meat = "fish" }); 25 } 26 } 27 /// <summary> 28 /// 爸爸 29 /// </summary> 30 public class Dad 31 { 32 public void Eat(object sender, EventArgs args) 33 { 34 var menArgs = (MenuArags)args; 35 Console.WriteLine($"儿子,我们今天晚上次:{menArgs.Greens},{menArgs.Fruid},{menArgs.Meat}"); 36 } 37 } 38 39 /// <summary> 40 /// 儿子 41 /// </summary> 42 public class Son 43 { 44 public void Eat(object sender, EventArgs args) 45 { 46 var menArgs = (MenuArags)args; 47 Console.WriteLine($"爸爸,我们今天晚上次:{menArgs.Greens},{menArgs.Fruid},{menArgs.Meat}"); 48 } 49 } 50 51 public class MenuArags : EventArgs 52 { 53 /// <summary> 54 /// 水果 55 /// </summary> 56 public string Fruid { set; get; } 57 58 /// <summary> 59 /// 青菜 60 /// </summary> 61 public string Greens { set; get; } 62 63 public string Meat { set; get; } 64 } 65 66 }