zoukankan      html  css  js  c++  java
  • 委托和事件

    一、委托

    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();

     +=委托的最后输出结果是什么是?请看下面的截图:

    可以看到,调用头部的委托导致了所有委托方法的执行。为委托+=增加方法让我们看起来像是委托被修改了,其实它们并没有被修改。事实上, 委托是不变的 。在给委托增加或移除方法时,实际发生的是创建了一个新的委托。

    从上面的截图中可以看出:多播委托的执行结果是把所有传入的方法都执行一遍,而且是按照实例化时传入方法的顺序依次执行的。(上面传入了两次当前类型的DoNoThing方法,所以会执行两边。)

    -= 为委托实例移除方法,从方法链的尾部开始匹配,遇到第一个完全吻合的,移除且只移除一个,没有也不异常。

    1 method -= new NoReturnNoPara(this.DoNothing);
    2 method -= new NoReturnNoPara(DoNothingStatic);
    3 method -= new NoReturnNoPara(Student.StudyAdvanced);
    4 method -= new NoReturnNoPara(new Student().Study);
    5 method.Invoke();

     移除委托的执行结果是什么呢?在上面添加委托的时候传入了5个方法,移除委托的时候移除了4个方法,应该只会执行DoNothing()这一个方法,是这样的吗?看看下面的运行结果:

    从截图中可以看出,最后的结果和我们猜测的结果不同,除了执行DoNothing()方法以外,还执行了Study()方法,但是添加的时候我们只添加了一个Study()方法,而且后面又移除掉了,那为什么还会执行这个方法呢?原因是因为添加和移除时候不是同一个实例的Study()方法(添加和移除的时候都是new了一个新实例),所以移除的时候不会被移除掉。怎么证明上面的原因是否正确呢?请看下面的代码:

    Console.WriteLine("***多播委托添加方法***");
    // 实例化一个Student对象
    Student student = new Student();
    // 实例化委托
    NoReturnNoPara method = new NoReturnNoPara(DoNothing);
    method += new NoReturnNoPara(this.DoNothing);
    method += new NoReturnNoPara(DoNothingStatic);
    method += new NoReturnNoPara(Student.StudyAdvanced);
    method += new NoReturnNoPara(new Student().Study);
    method += new NoReturnNoPara(student.Study);
    method.Invoke();
    
    Console.WriteLine("***下面是多播委托移除方法***");
    //-= 为委托实例移除方法,从方法链的尾部开始匹配,遇到第一个完全吻合的,移除且只移除一个,没有也不异常
    method -= new NoReturnNoPara(this.DoNothing);
    method -= new NoReturnNoPara(DoNothingStatic);
    method -= new NoReturnNoPara(Student.StudyAdvanced);//不是同一个实例,所以是不同的方法
    method -= new NoReturnNoPara(student.Study);
    method.Invoke();

     查看运行结果:

    从运行结果中可以看出上面的原因是正确的。

    注意:多播委托不能异步调用(即调用BeginInvoke()),因为多播委托里面有很多方法,异步调用的时候不知道该怎样执行,是把所有方法同步执行呢还是按照顺序依次执行呢,BeginInvoke不知道该如何调用,所以多播委托不能直接调用BeginInvoke()方法。那如果我想使用该怎么办呢?可以使用GetInvocationList()方法,F12查看GetInvocationList()方法的定义:

    那么可以使用如下的代码:

    1 foreach(NoReturnNoPara item in method.GetInvocationList())
    2 {
    3       item.Invoke();
    4 }

    上面的多播委托例子中一直都是使用的没有返回值的委托,如果是有返回值的委托,那么返回值是什么呢?请看下面的例子:

    先定义几个有返回值的方法:

     1 private int GetSomething()
     2 {
     3        return 1;
     4 }
     5 private int GetSomething2()
     6 {
     7        return 2;
     8 }
     9 private int GetSomething3()
    10 {
    11         return 3;
    12 }

     实例化委托:

    1 WithReturnNoPara methodWithReturn = new WithReturnNoPara(this.GetSomething);
    2 methodWithReturn += new WithReturnNoPara(this.GetSomething2);
    3 methodWithReturn += new WithReturnNoPara(this.GetSomething3);
    4 int iResult = methodWithReturn.Invoke();
    5 Console.WriteLine("返回值:"+iResult.ToString());

    运行程序查看结果:

    从截图中可以看出:带返回值的多播委托的结果是最后添加的方法的返回值。中间方法的返回值都会被丢弃。

    总结:多播委托一般用来调用无返回值的方法,不用来调用有返回值的方法,因为有返回值的多播委托中间的结果都会被丢弃掉。

    二、总结

    我们可以对委托做如下的总结:

    1. 委托是不可变的。
    2. 使用+=或-=操作符时,实际上是创建了新的委托实例,并把它赋给当前的委托变量。
    3. 如果多播委托的返回类型不是void,那么调用者从最后一个被调用的方法来接收返回值,前面的方法仍然会被调用,但是其返回值就被弃用了。
    4. 所有的委托类型都派生于System.MulticastDelegate,而它又派生于System.Delegate。
    5. C#会把作用于委托的+、-、+=、-=操作编译成使用System.Delegate的Combine和Remove两个静态方法。

    二、事件

    1、什么是事件

    事件是带event关键字的委托的实例。

    2、如何声明事件

    1 // 声明委托
    2 public delegate void MiaoDelegate();
    3 // 声明事件
    4 public event MiaoDelegate MiaoDelegateHandlerEvent;

    3、委托和事件的区别和联系

    委托是一个类型,例如Student类。

    事件是委托类型的一个实例,例如具体的一个学生。

    4、为什么要是有事件

    事件不能直接执行Invoke()方法,可以限制变量被外部调用或者直接赋值。

    注意:即使是在子类中,事件也不能调用Invoke()方法。

    三、委托和事件的应用

    来看下面的一个例子:

    有一个Cat类,里面有一个Miao()的方法,猫叫了一声,然后触发一系列的后续动作,通常的实现代码如下:

    public void Miao()
    {
         Console.WriteLine("{0} Miao", this.GetType().Name);
    
          new Mouse().Run();
          new Baby().Cry();
          new Mother().Wispher();
          new Father().Roar();
          new Neighbor().Awake();
          new Stealer().Hide();
          new Dog().Wang();
    }

    调用Miao()方法:

    1 // 实例化
    2 Cat cat = new Cat();
    3 cat.Miao();

    上面的代码可以实现上述的需求,但是这段代码耦合性很强,因为是在Miao()方法里面直接调用别的实例的方法,以后无论是增加或者修改、调整方法的调用顺序,都要修改Miao()方法,使得Miao()方法不稳定。

    下面使用委托来优化上面的代码:

     1 // 声明委托
     2 public delegate void MiaoDelegate();
     3 public MiaoDelegate MiaoDelegateHandler;
     4 public void MiaoNew()
     5 {
     6      Console.WriteLine("{0} MiaoNew", this.GetType().Name);
     7      if (this.MiaoDelegateHandler != null)
     8      {
     9           this.MiaoDelegateHandler.Invoke();
    10      }
    11 }

    调用:

     1 Cat cat = new Cat();
     2 // 多播委托
     3 cat.MiaoDelegateHandler += new MiaoDelegate(new Mouse().Run);
     4 cat.MiaoDelegateHandler += new MiaoDelegate(new Baby().Cry);
     5 cat.MiaoDelegateHandler += new MiaoDelegate(new Mother().Wispher);
     6 cat.MiaoDelegateHandler += new MiaoDelegate(new Brother().Turn);
     7 cat.MiaoDelegateHandler += new MiaoDelegate(new Father().Roar);
     8 cat.MiaoDelegateHandler += new MiaoDelegate(new Neighbor().Awake);
     9 cat.MiaoDelegateHandler += new MiaoDelegate(new Stealer().Hide);
    10 cat.MiaoDelegateHandler += new MiaoDelegate(new Dog().Wang);
    11 cat.MiaoNew();

    上面的委托也可以改为事件实现:

     1 // 声明事件
     2 public event MiaoDelegate MiaoDelegateHandlerEvent;
     3 public void MiaoNewEvent()
     4 {
     5      Console.WriteLine("{0} MiaoNewEvent", this.GetType().Name);
     6      if (this.MiaoDelegateHandlerEvent != null)
     7      {
     8           this.MiaoDelegateHandlerEvent.Invoke();
     9      }
    10 }

    调用:

     1 Cat cat = new Cat();
     2 cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Mouse().Run);
     3 cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Baby().Cry);
     4 cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Mother().Wispher);
     5 cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Brother().Turn);
     6 cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Father().Roar);
     7 cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Neighbor().Awake);
     8 cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Stealer().Hide);
     9 cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Dog().Wang);
    10 cat.MiaoNewEvent();
  • 相关阅读:
    浏览器能正常访问的url,superagent不能正常访问
    Reactor模式理解
    牛客网剑指offer 二维数组的查找
    在C语言结构体中添加成员函数
    html页面字体相关
    html页面背景设定相关
    快速排序
    html页面边框的另一种写法
    2018暑期北航软件能力培养师资培训有感
    web.xml文件介绍
  • 原文地址:https://www.cnblogs.com/dotnet261010/p/9094811.html
Copyright © 2011-2022 走看看