委托和事件
Posted .NET开发菜鸟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了委托和事件相关的知识,希望对你有一定的参考价值。
一、委托
1、什么是委托
委托是面向对象的、类型安全的,是引用类型。使用delegate关键字进行定义。委托的本质就是一个类,继承自System.MulticastDelegate,而它又派生自System.Delegate。里面内置了几个方法 ,可以在类的外面声明委托,也可以在类的内部声明委托。
对委托的使用:先定义,后声明和实例化委托,然后作为参数传递给方法。
1.1 定义委托
下面是几种委托定义的例子:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace MyDelegateDemo 8 { 9 // 也可以在类的外面定义委托 10 public delegate void NoReturnNoParaOutClass(); 11 12 public class MyDelegate 13 { 14 // 声明无参数无返回值的泛型委托 15 public delegate void NoReturnNoPara<T>(T t); 16 // 声明无参数无返回值的委托 17 public delegate void NoReturnNoPara(); 18 // 声明有参数无返回值的委托 19 public delegate void NoReturnWithPara(int x, int y); 20 // 声明无参数有返回值的委托 21 public delegate int WithReturnNoPara(); 22 // 声明有参数有返回值的委托 23 public delegate string WithReturnWithPara(out int x,ref int y); 24 } 25 }
1.2 声明并实例化委托
实例化委托时参数传递的是一个方法,方法的签名必须和委托的签名一样(即方法的返回值类型、参数列表的参数类型都必须和定义的委托一致)。
1 // 委托的实例化,DoNothing是一个方法 2 // NoReturnNoPara是定义的无参无返回值的委托,所以DoNothing方法也必须是无参无返回值的 3 NoReturnNoPara method = new NoReturnNoPara(DoNothing);
DoNothing()方法定义如下:
1 private void DoNothing() 2 { 3 Console.WriteLine("This is DoNothing"); 4 }
1.3 委托实例的调用
1 // 调用委托 2 method.Invoke(); 3 // Invoke也可以去掉 4 method();
注意:委托的调用和直接执行方法的效果是一样的,例如:
1 // 调用委托 2 method.Invoke(); 3 // Invoke也可以去掉 4 method(); 5 // 直接执行方法 6 this.DoNothing();
在控制台的Main()方法里面,结果如下:
从截图中能够看出:三种方式的输出结果都是一样的。
2、委托类型和委托实例
委托类型:定义了委托实例可以调用的那类方法,具体来说,委托类型定义了方法的返回类型和参数类型,下面的代码定义了一个委托类型:
// 规定了可以调用的方法的返回值类型是int,有一个类型为int的参数 delegate int Transformer(int x);
委托实例:把方法赋值给委托变量的时候就创建了委托实例,例如下面的代码:
Transformer t =new Transformer(Square);
也可以简写为下面的形式:
Transformer t = Square;
Square是定义的一个方法,其方法定义如下:
int Square(int x) { return x*x; }
委托的实例其实就是调用者的委托:调用者调用委托,然后委托调用目标方法,间接的把调用者和目标方法解耦合。
讲到这里可能有人会问:既然使用委托和直接调用方法的效果是一样的,那为什么还要使用委托呢,直接调用方法多么简单?下面先来看一个实际的例子。
先定义一个Student类,里面有一些属性和方法,Student类定义如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace MyDelegateDemo 8 { 9 public class Student 10 { 11 public int Id { get; set; } 12 public string Name { get; set; } 13 public int ClassId { get; set; } 14 public int Age { get; set; } 15 16 public static void Show() 17 { 18 Console.WriteLine("123"); 19 } 20 } 21 }
然后使用集合初始化器的方式初始化一个List<Student>集合,填充一些测试数据:
1 private List<Student> GetStudentList() 2 { 3 #region 初始化数据 4 List<Student> studentList = new List<Student>() 5 { 6 new Student() 7 { 8 Id=1, 9 Name="老K", 10 ClassId=2, 11 Age=35 12 }, 13 new Student() 14 { 15 Id=1, 16 Name="hao", 17 ClassId=2, 18 Age=23 19 }, 20 new Student() 21 { 22 Id=1, 23 Name="大水", 24 ClassId=2, 25 Age=27 26 }, 27 new Student() 28 { 29 Id=1, 30 Name="半醉人间", 31 ClassId=2, 32 Age=26 33 }, 34 new Student() 35 { 36 Id=1, 37 Name="风尘浪子", 38 ClassId=2, 39 Age=25 40 }, 41 new Student() 42 { 43 Id=1, 44 Name="一大锅鱼", 45 ClassId=2, 46 Age=24 47 }, 48 new Student() 49 { 50 Id=1, 51 Name="小白", 52 ClassId=2, 53 Age=21 54 }, 55 new Student() 56 { 57 Id=1, 58 Name="yoyo", 59 ClassId=2, 60 Age=22 61 }, 62 new Student() 63 { 64 Id=1, 65 Name="冰亮", 66 ClassId=2, 67 Age=34 68 }, 69 new Student() 70 { 71 Id=1, 72 Name="瀚", 73 ClassId=2, 74 Age=30 75 }, 76 new Student() 77 { 78 Id=1, 79 Name="毕帆", 80 ClassId=2, 81 Age=30 82 }, 83 new Student() 84 { 85 Id=1, 86 Name="一点半", 87 ClassId=2, 88 Age=30 89 }, 90 new Student() 91 { 92 Id=1, 93 Name="小石头", 94 ClassId=2, 95 Age=28 96 }, 97 new Student() 98 { 99 Id=1, 100 Name="大海", 101 ClassId=2, 102 Age=30 103 }, 104 new Student() 105 { 106 Id=3, 107 Name="yoyo", 108 ClassId=3, 109 Age=30 110 }, 111 new Student() 112 { 113 Id=4, 114 Name="unknown", 115 ClassId=4, 116 Age=30 117 } 118 }; 119 #endregion 120 return studentList; 121 }
现在有一个需求,找出List<Student>集合里面年龄大于25的学生信息,代码如下:
List<Student> studentList = this.GetStudentList(); //找出年龄大于25 List<Student> resultAge = new List<Student>();//准备容器 foreach (Student student in studentList)//遍历数据源 { if (student.Age > 25)//判断条件 { resultAge.Add(student);//满足条件的放入容器 } } Console.WriteLine($"结果一共有{resultAge.Count()}个");
使用一个foreach循环很容易得到全部年纪大于25的学生,这时又提出了需求:找出name长度大于2的学生、找出Name长度大于2 而且年龄大于25 而且班级id是2的学生,代码如下:
1 //找出Name长度大于2 2 List<Student> resultName = new List<Student>(); 3 foreach (Student student in studentList) 4 { 5 if (student.Name.Length > 2) 6 { 7 resultName.Add(student); 8 } 9 } 10 Console.WriteLine($"结果一共有{resultName.Count()}个"); 11 12 //找出Name长度大于2 而且年龄大于25 而且班级id是2 13 List<Student> result = new List<Student>(); 14 foreach (Student student in studentList) 15 { 16 if (student.Name.Length > 2 && student.Age > 25 && student.ClassId == 2) 17 { 18 result.Add(student); 19 } 20 } 21 Console.WriteLine($"结果一共有{result.Count()}个");
观察上面的代码,你会发现里面有很多重复的代码:每次都要先准备一个查询结果集的集合,然后遍历数据源,判断条件,把满足条件的放入到集合中。可不可以把上面的代码进行优化呢?请看下面的代码:
1 private List<Student> GetList(List<Student> source, int type) 2 { 3 List<Student> result = new List<Student>(); 4 foreach (Student student in source) 5 { 6 switch(type) 7 { 8 case 1: 9 if (student.Age > 25) 10 { 11 result.Add(student); 12 } 13 break; 14 case 2: 15 if (student.Name.Length > 2) 16 { 17 result.Add(student); 18 } 19 break; 20 case 3: 21 if (student.Name.Length > 2 && student.Age > 25 && student.ClassId == 2) 22 { 23 result.Add(student); 24 } 25 break; 26 } 27 } 28 return result; 29 }
在上面这段代码中,每次根据不同的type类型执行不同的判断条件,这样看起来可以把一些重复的代码进行了重用,但是这样又会有其他的问题:所有的判断逻辑都写在了一起,如果又增加了一种type类型或者判断逻辑改变了,就要修改整个代码,违反了开闭原则。
仔细观察上面的这段代码:GetList()方法需要传入一个int类型的参数,根据不同的参数,执行对应的逻辑。那么可不可以直接传递逻辑进来呢?逻辑就是方法,也就是说能不能传递一个方法进来。可能有人会问题,方法都是进行调用啊,怎么能进行传递呢?答案是肯定的:那就是使用上面讲到的委托。
以查询年龄大于25的学生为例:逻辑就是判断学生的年龄是否大于25,返回一个bool值,如果大于25就添加到集合中,根据逻辑,可以得到下面的方法:
1 private bool Than(Student student) 2 { 3 return student.Age > 25; 4 }
根据这个方法的签名可以定义如下的委托:
1 // 定义委托 2 public delegate bool ThanDelegate(Student student);
修改上面GetList的方法,把委托作为参数传递进来:
1 private List<Student> GetListDelegate(List<Student> source, ThanDelegate method) 2 { 3 List<Student> result = new List<Student>(); 4 foreach (Student student in source) 5 { 6 // 调用委托 7 if (method.Invoke(student)) 8 { 9 result.Add(student); 10 } 11 } 12 return result; 13 }
实例化委托:
1 // 实例化委托 2 ThanDelegate method = new ThanDelegate(this.Than); 3 List<Student> resultDele = this.GetListDelegate(studentList, method); 4 Console.WriteLine($"结果一共有{resultDele.Count()}个");
另外两个可以定义如下的方法:
1 /// <summary> 2 /// 查询Name长度大于2 3 /// </summary> 4 /// <param name="student"></param> 5 /// <returns></returns> 6 private bool LengthThan(Student student) 7 { 8 return student.Name.Length > 2; 9 } 10 11 /// <summary> 12 /// 查询Name长度大于2 而且年龄大于25 而且班级id是2 13 /// </summary> 14 /// <param name="student"></param> 15 /// <returns></returns> 16 private bool AllThan(Student student) 17 { 18 return student.Name.Length > 2 && student.Age > 25 && student.ClassId == 2; 19 }
实例化委托如下:
1 //Name长度大于2 2 ThanDelegate nameMethod = new ThanDelegate(LengthThan); 3 List<Student> nameList= this.GetListDelegate(studentList, nameMethod); 4 Console.WriteLine($"Name长达大于2的结果一共有{nameList.Count()}个"); 5 6 //Name长度大于2 而且年龄大于25 而且班级id是2 7 ThanDelegate allMethod = new ThanDelegate(AllThan); 8 List<Student> allList = this.GetListDelegate(studentList, allMethod); 9 Console.WriteLine($"Name长度大于2 而且年龄大于25 而且班级id是2的结果一共有{nameList.Count()}个");
观察GetListDelegate这个方法:保留了以前公用的代码:准备一个结果集的集合、循环遍历数据源,把符合条件的学生添加到结果集中,而判断逻辑放到了单独的一个方法中,如果判断逻辑改变了或者需要增加新的判断逻辑,只需要修改原有的判断逻辑或者新增判断逻辑即可,这样可以做到不需要修改GetListDelegate()这个方法,很好的符合开不原则。
可以总结出委托的一个应用:委托可以解除公用逻辑(准备结果集的集合、循环遍历数据源,添加到结果集中)和具体的业务逻辑(例如判断年龄大于25)的耦合,可以减少重复的代码。
2、多种途径实例化委托
委托实例化的时候不仅可以传入当前类型的普通方法,还可以传入静态、实例方法等,例如:
1 // 传入当前类型的普通方法 2 NoReturnNoPara method = new NoReturnNoPara(DoNothing); 3 // 传入当前类型的静态方法 4 NoReturnNoPara methodStatic = new NoReturnNoPara(DoNothingStatic); 5 // 传入其他类型的静态方法 6 NoReturnNoPara methodOtherStaitc = new NoReturnNoPara(Student.StudyAdvanced); 7 // 传入其他类型的普通方法 8 NoReturnNoPara methodOther = new NoReturnNoPara(new Student().Study);
其中DoNothingStatic()方法定义如下:
1 private void DoNothingStatic() 2 { 3 Console.WriteLine("This is DoNothingStatic"); 4 }
Student类的静态方法和实例方法定义如下:
1 public static void StudyAdvanced() 2 { 3 Console.WriteLine("欢迎学习高级班课程"); 4 } 5 6 public void Study() 7 { 8 Console.WriteLine("学习"); 9 }
总结:实例化委托时传入的方法只有一个要求:方法的签名和委托的签名一样,即返回值类型和参数列表一致,无论该方法来自于当前类型的普通方法、静态方法或者其他类型的实例方法和静态方法。
3、链式委托
链式委托也被称为“多播委托”,其本质是 一个由多个委托组成的链表 。我们知道,所有的自定义委托都继承自System.MulticastDelegate类,这个类就是为链式委托而设计的。当两个及以上的委托被链接到一个委托链时,调用头部的委托将导致该链上的所有委托方法都被执行。
像上面实例化委托的时候,一个委托类型的变量只能保存一个方法,使用多播委托,一个委托类型的变量可以保存多个方法,多播委托可以增加、减少委托,Invoke的时候可以按顺序执行。
+= 为委托实例按顺序增加方法,形成方法链,Invoke时,按顺序依次执行,例如下面的代码:
1 // 实例化委托 2 NoReturnNoPara method = new NoReturnNoPara(DoNothing); 3 method += new NoReturnNoPara(this.DoNothing); 4 method += new NoReturnNoPara(DoNothingStatic); 5 method += new NoReturnNoPara(Student.StudyAdvanced); 6 method += new NoReturnNoPara(new Student().Study); 7 method.Invoke();
+=委托的最后输出结果是什么是?请看下面的截图:
可以看到,调用头部的委托导致了所有委托方法的执行。为委托+=增加方法让我们看起来像是委托被修改了,其实它们并没有被修改。事实上, 委托是不变的 。在给委托增加或移除方法时,实际发生的是创建了一个新的委托。
从上面的截图中可以看出:多播委托的执行结果是把所有传入的方法都执行一遍,而
以上是关于委托和事件的主要内容,如果未能解决你的问题,请参考以下文章