C# 委托(二)—— 多播委托与事件

目录

1 多播委托

1.1 多播委托的实例化

1.2 多播委托的调用与返回结果

1.3 多播委托的逐个调用

2 事件

2.1 事件的本质

2.2 事件用法三步曲

2.3 事件与多播委托的区别


上一篇文章中,介绍了委托的基本用法(传送门:https://blog.csdn.net/wnvalentin/article/details/81840339)。本文中,我们来了解一下常用的事件机制,以及事件的基石——多播委托。

1 多播委托

所谓多播委托,即 “多路广播委托”(MulticastDelegate)。从它的名字就可以看出,此种委托可以像广播一样将影响信息“传播”到四面八方。多播委托类拥有一个方法调用列表,调用委托时,它就会逐一调用该列表中的方法,从而实现多重影响。

MulticastDelegate 位于 System 命名空间下,它派生于 Delegate 类,声明、调用方法与普通委托相同,但是实例化方法不太一样。

1.1 多播委托的实例化

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

我们结合案例进行说明。

我们定义一个简单的执行加减运算的委托,并利用+=运算符和Lambda表达式为该多播委托添加两个方法:

public delegate int AddDelegate(int a);

AddDelegate myAddDelegate = new AddDelegate(i => i + 10);
myAddDelegate += i => i - 10;

 看到这里,细心的同学可能有个问题了,我们声明的是个普通的委托,为啥能当多播委托来实例化呢?实际上,普通的Delegate类用途不是很大,而我们 自定义的委托,以及常用的内置委托如Func<T>,都是派生于多播委托的!我们可以验证一下:

输入

var type = typeof(AddDelegate);
Console.WriteLine($"AddDelegate委托的基类为{type.BaseType.FullName}");

输出为:

AddDelegate委托的基类为System.MulticastDelegate

1.2 多播委托的调用与返回结果

调用上面的AddDelegate,传入一个参数10:

Console.WriteLine(myAddDelegate?.Invoke(10));

 注意多播委托的调用,由于调用列表中的方法可能被 -= 运算符全部删除,为了避免调用空委托,需要用?.进行调用。

运行上述代码,得到结果:

0

 奇了怪了,不是有两个方法么?怎么只有一个返回值呢???

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

因此,一个具有非空返回值的多播委托通常是没有意义的,因为只能获得最后一个方法的返回结果。通常,多播委托的返回类型为 void。

1.3 多播委托的逐个调用

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

//逐个调用
Console.WriteLine("逐个调用:");
foreach (AddDelegate f in myAddDelegate.GetInvocationList())
    Console.WriteLine(f.Invoke(10));

 如此便可逐个得到方法的输出结果:

逐个调用:
20
0

2 事件

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

2.1 事件的本质

事件是一种委托,具体的说来,事件是一种名为 EventHandler<TEventArgs>  的泛型委托。它是.NET为我们实现事件而专门提供的委托类(微软大法好)。其中的泛型类型 TEventArgs 代表着自定义事件的详细信息类。我们来看该委托的定义:

从定义中可看出,事件委托采用了两个参数: sender 和 泛型参数 TEventArgs。其中 sender 代表事件源,是object类型的,所以我们可以传入任何自定义的事件触发对象。第二个参数就是实例化该泛型委托时时传入的实际类型,代表着事件参数,它必须派生于 EventArgs 类,我们可以建立这个事件参数类,通过为该类添加自定义属性来加入任何你想要的事件信息。

2.2 事件用法三步曲

事件机制的使用方法可以归纳为3个步骤:

(1)事件发布者定义event以及事件相关信息  (2)事件侦听者订阅event   (3)事件发布者触发event,自动调用订阅者的事件处理方法。

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

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

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

public class CarInfoEventArgs : EventArgs
{
     public string Car { get; }
     public CarInfoEventArgs(string car)
     {
          Car = car;
     }
}

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

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

public class CarDealer
{
     public event EventHandler<CarInfoEventArgs> NewCarEvent;   
}

 事件用 event 关键字来定义。Event 是对多播委托的一种封装。详见下文。事件一般作为事件发布者的一个成员,定义在发布者内部。

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

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

public class Consumer
{
     private readonly string _name;
     public Consumer(string name)
     {
          _name = name;
     }

     public void KonwsNewCarArrived(object sender, CarInfoEventArgs e)
     {
          Console.WriteLine($"  {_name}: OK, I learn that car {e.Car} arrived.");
     }
}

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

var dealer = new CarDealer();
var Tom = new Consumer("Mike");
var Mary = new Consumer("Mary");

dealer.NewCarEvent += new EventHandler<CarInfoEventArgs>(Tom.KonwsNewCarArrived);
dealer.NewCarEvent += Mary.KonwsNewCarArrived;

3. 事件发布者触发事件

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

public class CarDealer
{
     public event EventHandler<CarInfoEventArgs> NewCarEvent;

     public void NewCarArrives(string car)
     {
         Console.WriteLine($"CarDealer: Attention, new car {car} arrives!!!");
         NewCarEvent?.Invoke(this, new CarInfoEventArgs(car));
     }
}

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

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.

与多播委托一样,也可通过 -= 运算符取消订阅事件。

2.3 事件与多播委托的区别

前面说,事件就是多播委托的一种封装。那么,如果不使用事件,直接使用委托,能不能实现事件机制呢?答案是完全可以。读者可声明一个 public Action<object,CarInfoEventArgs> NewCarDelegate 委托来代替案例中的 EventHandler 事件,其他处理与事件完全相同,可实现相同的结果。

那么,为什么还要用事件呢?事件是怎么封装了委托呢?

实际上,在以上案例中,使用 event 关键字在一行上定义事件是C# 提供的事件的简化记法。在我们定义了上述事件后,编译器会自动生成如下代码:

private EventHandler<CarInfoEventArgs> newCarEvent;
public event EventHandler<CarInfoEventArgs> NewCarEvent
{
    add
    {
        newCarEvent += value;
    }
    remove
    {
        newCarEvent -= value;
    }
}

这非常像字段及其属性的关系。注意到,委托字段 newCarEvent 是私有的,因此在外部不能直接为事件赋值,但可以通过公开的 += 和 -= 运算符为事件添加实例方法。 另外,事件 event 是一种数据类型,是一个已经声明的委托类,只能在某个类的内部声明,并且只能被该类调用,不能在命名空间中声明和使用。这一点与委托 delegate 的声明不同。

总结:事件与委托的区别在于两点:

(1)委托是一个类,可以在命名空间中声明;而事件只能在事件发布者内部定义,且只能被该类调用。

(2)可以直接使用一个方法为委托赋值,而事件只开放了 += 和 -= 运算符为其添加或删除方法。

  • 21
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C#中,事件委托是密切相关的概念。委托是一种类型,它封装了一个或多个方法,并允许将这些方法作为参数传递给其他方法。事件则是一种特殊的委托,它允许程序员在对象发生特定的操作或状态改变时通知其他对象。 在C#中,可以使用delegate关键字定义委托类型。例如: ``` public delegate void MyDelegate(int x, int y); ``` 这个代码定义了一个名为MyDelegate的委托类型,它封装了一个具有两个int参数的方法。可以使用这个委托类型来声明变量,例如: ``` MyDelegate myDelegate = new MyDelegate(MyMethod); ``` 这个代码创建了一个名为myDelegate的变量,它引用了一个具有两个int参数的方法MyMethod。 事件通常使用EventHandler委托类型作为事件处理程序的签名。例如: ``` public class MyClass { public event EventHandler MyEvent; } ``` 这个代码定义了一个名为MyEvent的事件,它使用EventHandler委托类型作为事件处理程序的签名。可以使用“+=”运算符将事件处理程序添加到事件的处理程序列表中。例如: ``` MyClass myObject = new MyClass(); myObject.MyEvent += new EventHandler(MyEventHandler); ``` 这个代码将MyEventHandler方法添加到MyClass对象的MyEvent事件的处理程序列表中。 需要注意的是,事件只能在事件发布者类中声明和触发,而事件处理程序则可以在事件订阅者类中定义。事件的订阅者可以使用委托实例来订阅事件,并在事件发生时执行相应的操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值