C#技术栈入门到精通系列4——委托和事件

阅读目录

1、介绍

2、委托的声明、实例化及调用

  2.1、声明委托

  2.2、实例化委托

  2.3、调用委托

3、泛型委托
  3.1、Func<T>委托

  3.2、Action<T>委托

4、Lambda

5、多播委托

  5.1、多播委托实例化

  5.2、多播委托调用和返回结果

  5.3、多播委托的逐个调用

6、事件

  6.1、事件的本质

  6.2、事件用法三部曲

  6.3、事件和多播委托区别

7、参考

返回系列文章目录 

源代码下载

1、介绍

  委托的定义和语法和方法很相识,只比方法多了一个修饰关键字 delegate  ,我们都知道方法是将类型参数化,所谓类型参数化的意思就是把数据类型作为参数传递,使一个方法可以操作多种数据类型。我们这里说的委托就是将方法参数化,把方法作为参数来传递。

2、委托的申明和实例化

2.1、声明委托

   委托是一种特殊的类,因此委托的声明与类的声明方法类似,在任何可以声明类的地方都可以声明委托。委托声明用 delegate 关键字,同时委托要指明参数和返回值,写法与方法类似。委托声明写成如下形式:

  public delegate void MyDelegate();   

  public:访问修饰符 

  delegate:关键字 

  void:返回类型 

  MyDelegate:委托名称 

  ( ):参数列表

   委托的声明实际上是定义了一个派生于System.Delegate类的类,这与一般类的声明语法不同。编译器会根据委托的声明自动创建一个委托的类并实现细节。             

2.2、实例化委托

   与普通类的使用方法相同,声明了委托之后,我们必须给委托传递一个具体的方法进行实例化,实例化过后才能在运行时调用。委托实例包含了被传递给它的方法的信息,在运行时,调用委托实例就相当于执行它当中的方法。委托实例化的方法如下: 委托类名 委托实例名 = new 委托类名(Target) ;      其中,委托实例名是自定义的名称,Target是要传入的方法的名称。注意,Target是方法的引用,不能带()。带()的话是该方法的调用。区分引用和调用。实例化方法可以简写为:委托类名 委托实例名 = Target;  在需要委托实例的地方直接传入Target引用即可,C#编译器会自动根据委托类型进行验证,这称为“委托推断”。  

2.3、调用委托

  委托实例等价于它当中实际方法,因此可以使用反射的Invoke()方法调用委托实例,也可以直接在委托实例后加上()进行调用。

3、泛型委托
3.1、Func<T>委托

  Func<T>委托代表着拥有返回值的泛型委托。Func<T>有一系列的重载,形式如 Func<T1,T2, ... TResult>,其中TResult代表委托的返回值类型,其余均是参数类型。只有一个T时,即Func<TResult>,代表该委托是无参数的。.NET封装了最多16个输入参数的Funct<>委托。需要意的是,若方法返回 void ,由于 void 不是数据类型,因此不能定义Func<void>委托。返回 void 的泛型委托见下文的Action<T>。 

 1 internal class LearnFunc
 2     {
 3         public Func<string, string, bool> MyCompareFunc; //声明Func泛型委托
 4 
 5         public bool CompareString(string str1,string str2)
 6         {
 7             return str1== str2;
 8         }
 9 
10         public void Test()
11         {
12             //实例化
13             MyCompareFunc = new Func<string, string, bool>(CompareString);  
14             //可以简写为  MyCompareFunc = CompareString;
15 
16             //调用委托两种方式
17             var result1 = MyCompareFunc.Invoke("I love China","I Love China");
18             var result2 = MyCompareFunc("I love China", "I Love China");
19         }
20 
21     }
泛型委托声明、实例化及调用

3.2、Action<T>委托

  Action<T>委托代表返回值为空 void 的委托,它也有一些列重载,最多拥有16个输入参数。用法与Func<T>相同。

4、Lambda

  Lambda表达式本质上是改进的匿名方法。Lambda表达式把其中的箭头用 => 符号表示。如今Lambda表达式已经应用在很多地方了,例如方法体表达式(Expression-Bodied Methods)、自动只读属性表达式等等。Lambda表达式形式上分为两种:表达式Lambda、语句Lambda。

  当匿名函数只有一行代码时,可采用这种形式。例如:

1 CompareDelegate LambdaCompare = (s4, s5) => s4.Age <= s5.Age;
表达式Lambda

其中=>符号代表Lambda表达式,它的左侧是参数,右侧是要返回或执行的语句。参数要放在圆括号中,若只有一个参数,为了方便起见可省略圆括号。有多个参数或者没有参数时,不可省略圆括号。相比匿名函数,在表达式Lambda中,方法体的花括号{}和return关键字被省略掉了。

  

  当匿名函数有多行代码时,只能采用语句Lambda。例如,上面的表达式Lambda可改写为语句Lambda:

1 CompareDelegate LambdaCompare = (s4, s5) => 
2 {
3 return s4.Age <= s5.Age;
4 };
语句Lambda

语句Lambda不可以省略{}和return语句。

5、多播委托

  所谓多播委托,即 “多路广播委托”(MulticastDelegate)。从它的名字就可以看出,此种委托可以像广播一样将影响信息“传播”到四面八方。多播委托类拥有一个方法调用列表,调用委托时,它就会逐一调用该列表中的方法,从而实现多重影响。MulticastDelegate 位于 System 命名空间下,它派生于 Delegate 类,声明、调用方法与普通委托相同,但是实例化方法不太一样。

5.1、多播委托实例化

  多播委托的初始化可以像普通委托一样,传入一个签名相同的实例方法。同时,多播委托重载了 += 运算符和 -= 运算符,用来向其调用列表中添加或者删除方法。调用多播委托时,方法将按照添加的顺序被依次调用。

1 internal class MulticastDelegate
2     {
3         public delegate int AddDelegate(int a);  //声明
4         public void Test()
5         {
6             AddDelegate myAddDelegate = new AddDelegate(a => a + 10); //实例化
7             myAddDelegate += i => i - 10; //增加一路委托到myAddDelagate实例
8         }
9     }
多播委托实例化

5.2、多播委托调用和返回结果

  调用上面的AddDelegate,传入一个参数10:Console.WriteLine(myAddDelegate?.Invoke(10)); 注意多播委托的调用,由于调用列表中的方法可能被 -= 运算符全部删除,为了避免调用空委托,需要用?.进行调用。运行上述代码,得到结果: 0 奇了怪了,不是有两个方法么?怎么只有一个返回值呢???

  实际上,两个方法都被调用了。我们来分析一下:多播委托按照顺序调用其列表中的方法。本例中,首先,我们对参数10调用了 i => i + 10 函数,得到了本匿名函数返回值20。然而,委托的调用并没有停下来, 而是继续调用剩余的方法。然后继续对参数10调用 i => i - 10 函数,得到新的返回值0,上个函数的返回值被覆盖丢弃。至此委托调用结束,返回最后调用方法的返回结果。
  因此,一个具有非空返回值的多播委托通常是没有意义的,因为只能获得最后一个方法的返回结果。通常,多播委托的返回类型为 void。

 1 internal class MulticastDelegate
 2     {
 3         public delegate int AddDelegate(int a);  //声明
 4         public void Test()
 5         {
 6             AddDelegate myAddDelegate = new AddDelegate(a => a + 10); //实例化
 7             myAddDelegate += i => i - 10; //增加一路委托到myAddDelagate实例
 8 
 9             //调用
10             Console.WriteLine(myAddDelegate?.Invoke(10));
11         }
12     }
多播委托的调用

5.3、多播委托的逐个调用

  那么对于返回类型不为空的多播委托来说,有没有办法得到所有方法的返回结果呢?有的!多播委托提供了一个 GetInvocationList () 方法,通过它枚举出所有委托,然后按顺序获取并执行调用列表中的方法。

 1 internal class MulticastDelegate
 2     {
 3         public delegate int AddDelegate(int a);  //声明
 4         public void Test()
 5         {
 6             AddDelegate myAddDelegate = new AddDelegate(a => a + 10); //实例化
 7             myAddDelegate += i => i - 10; //增加一路委托到myAddDelagate实例
 8 
 9             //调用
10             Console.WriteLine(myAddDelegate?.Invoke(10));
11 
12             //逐个调用
13             foreach (AddDelegate item in myAddDelegate.GetInvocationList())
14             {
15                 Console.WriteLine(item?.Invoke(10));
16             }
17         }
18     }
多播委托逐个调用

6、事件

  事件机制是基于多播委托的。理解了多播委托,事件就不难理解。

6.1、事件的本质

  事件是一种委托,具体的说来,事件是一种名为 EventHandler<TEventArgs>  的泛型委托。它是.NET为我们实现事件而专门提供的委托类(微软大法好)。其中的泛型类型 TEventArgs 代表着自定义事件的详细信息类。从定义中可看出,事件委托采用了两个参数: sender 和 泛型参数 TEventArgs。其中 sender 代表事件源,是object类型的,所以我们可以传入任何自定义的事件触发对象。第二个参数就是实例化该泛型委托时时传入的实际类型,代表着事件参数,它必须派生于 EventArgs 类,我们可以建立这个事件参数类,通过为该类添加自定义属性来加入任何你想要的事件信息。

6.2、事件用法三部曲 

  事件机制的使用方法可以归纳为3个步骤:
  (1)事件发布者定义event以及事件相关信息
  (2)事件侦听者订阅event
  (3)事件发布者触发event,自动调用订阅者的事件处理方法。

  下面用案例来说明。构建一个场景:作为汽车经销商,当有新车到店时,会发布一个事件,通知订车的消费者,告诉他们汽车的相关信息,消费者接收事件通知后,进行相应的处理。这里,事件的发布者就是汽车经销商,事件的订阅者就是消费者。

1. 事件发布者定义event以及事件相关信息

作为事件发布者,可以首先定义自己的事件参数类 TEventArgs:

1 public class CarInfoEventArgs : EventArgs    //事件参数类
2 {
3   public string Car { get; }
4   public CarInfoEventArgs(string car)
5   {
6     Car = car;
7   }
8 }

这里我们给自定义事件参数类中加入了汽车的信息。

然后定义我们的经销商类 CarDealer,它拥有一个事件成员:

1 public class CarDealer
2 {
3   public event EventHandler<CarInfoEventArgs> NewCarEvent;  //定义事件
4 }

2. 事件订阅者(消费者)订阅事件

现在我们定义一个消费者类,它拥有一个事件处理方法 KnowsNewCarArrived,事件的订阅就是将该方法添加到事件的调用列表里。

 1 public class Consumer
 2 {
 3   private readonly string _name;
 4   public Consumer(string name)
 5   {
 6     _name = name;
 7   }
 8 
 9   public void KonwsNewCarArrived(object sender, CarInfoEventArgs e)
10   {
11     Console.WriteLine($" {_name}: OK, I learn that car {e.Car} arrived.");
12   }
13 }

现在我们在客户端中创建两个消费者对象,并订阅新车到达的事件:

1 var dealer = new CarDealer();
2 var Tom = new Consumer("Mike");
3 var Mary = new Consumer("Mary");
4 
5 dealer.NewCarEvent += new EventHandler<CarInfoEventArgs>(Tom.KonwsNewCarArrived);
6 dealer.NewCarEvent += Mary.KonwsNewCarArrived;

3. 事件发布者触发事件

事件是由事件发布者(经销商)触发的,为了触发新车到达事件,我们 给 CarDealer 类添加一个 NewCarArrives 方法,该方法调用事件的 Invoke() 方法,并传入事件参数。

 1 public class CarDealer
 2 {
 3   public event EventHandler<CarInfoEventArgs> NewCarEvent;
 4 
 5   public void NewCarArrives(string car)
 6   {
 7     Console.WriteLine($"CarDealer: Attention, new car {car} arrives!!!");
 8     NewCarEvent?.Invoke(this, new CarInfoEventArgs(car));
 9   }
10 }

最后在客户端调用该方法,实现事件的触发:

1 dealer.NewCarArrives("DasAuto");

触发事件后,注册到事件中的消费者方法被依次调用,输出如下:

CarDealer: Attention, new car DasAuto arrives!!!
Mike: OK, I learn that car DasAuto arrived.
Mary: OK, I learn that car DasAuto arrived.
与多播委托一样,也可通过 -= 运算符取消订阅事件。

6.3、事件和多播委托区别

  事件与委托的区别在于两点:
  (1)委托是一个类,可以在命名空间中声明;而事件只能在事件发布者内部定义,且只能被该类调用。
  (2)可以直接使用一个方法为委托赋值,而事件只开放了 += 和 -= 运算符为其添加或删除方法。

7、参考

wnvalentin博客:https://blog.csdn.net/wnvalentin/article/details/81840339

          https://blog.csdn.net/wnvalentin/article/details/82254656

HolyKnight博客:https://www.cnblogs.com/holyknight-zld/archive/2012/08/30/delegateEvent.html

原文地址:https://www.cnblogs.com/bigbox777/p/14414494.html