委托提供与C++中“函数指针”相同的功能,用于传递和调用函数的引用,是观察者模式的一种实现。
事件是用委托实现的,是对委托的额外封装,其本质上是一种特殊的委托。
事件的作用
- 封装订阅: 事件将委托的订阅操作进行封装,仅允许 += 和 -= 操作,避免程序员在开发时因误用 = 使得委托链断裂
- 封装发布: 事件确保只有包含它类才能触发事件通知,杜绝在委托中出现的“订阅者”也能触发
本篇使用分别使用委托和事件来实现简单的观察者模式例子,三个版本输出完全相同,为方便对比,使用了最原始的delegate语法。读者可以对比三版的不同之处来了解两者的区别。
委托版本
using System;
namespace LearningDelegate
{
class Program
{
static void Main(string[] args)
{
//出版社有一本叫《故事会》的杂志
Publisher publisher = new Publisher("《故事会》");
//读者小A订了这本杂志
Observer observerA = new Observer("小A");
publisher.Magazine += observerA.RecieveMagazine;
//读者小B也订了这本杂志
Observer observerB = new Observer("小B");
publisher.Magazine += observerB.RecieveMagazine;
//出版社印刷本月的《故事会》
publisher.PublishingMagazine();
Console.ReadLine();
}
}
//读者
class Observer
{
public Observer(string _name)
{
name = _name;
}
public string name;
public void RecieveMagazine(string message)
{
Console.WriteLine("{0}收到了{1}, 仔细阅读了一番。", this.name, message);
}
}
//出版社
class Publisher
{
public Publisher(string magName)
{
magazineName = magName;
}
public string magazineName;
public delegate void MagazineDelegate(string message);
public MagazineDelegate Magazine;
public void PublishingMagazine()
{
//如果没人订,就不用印了
//此处必须判断Delegate对象是否为空,调用空的Delegate对象会引发异常
if (Magazine != null)
{
Magazine(magazineName);
}
}
}
}
委托有一个特点:由于委托的订阅和触发都直接作用于delegate对象,这导致委托可以在可订阅的空间中被触发,也就是说我们无法将委托的触发封装起来。而事件event对象只能在其定义的类中被触发。
一个最最简单的事件版本
using System;
namespace LearningDelegate
{
class Program
{
static void Main(string[] args)
{
//出版社有一本叫《故事会》的杂志
Publisher publisher = new Publisher("《故事会》");
//读者小A订了这本杂志
Observer observerA = new Observer("小A");
publisher.Magazine += observerA.RecieveMagazine;
//读者小B也订了这本杂志
Observer observerB = new Observer("小B");
publisher.Magazine += observerB.RecieveMagazine;
//出版社印刷本月的《故事会》
publisher.PublishingMagazine();
Console.ReadLine();
}
}
//读者
class Observer
{
public Observer(string _name)
{
name = _name;
}
public string name;
public void RecieveMagazine(string message)
{
Console.WriteLine("{0}收到了{1}, 仔细阅读了一番。", this.name, message);
}
}
//出版社
class Publisher
{
public Publisher(string megName)
{
magazineName = megName;
}
public string magazineName;
public delegate void MagazineDelegate(string message);
//使用自定义的委托类型和event关键字创建事件对象
public event MagazineDelegate Magazine;
public void PublishingMagazine()
{
//如果没人订,就不用印了
//此处必须判断事件对象是否为空
if (Magazine != null)
{
Magazine(magazineName);
}
}
}
}
这个最最简单的事件,就是给原有的委托加了一层event关键字的封装,增加了上述的两个特性。这个简单的例子只能用于了解委托和事件语法的差别,下面给出标准的事件语法的版本。
标准的事件版本
using System;
namespace LearningDelegate
{
class Program
{
static void Main(string[] args)
{
//出版社有一本叫《故事会》的杂志
Publisher publisher = new Publisher("《故事会》");
//读者小A订了这本杂志
Observer observerA = new Observer("小A");
publisher.Magazine += observerA.RecieveMagazine;
//读者小B也订了这本杂志
Observer observerB = new Observer("小B");
publisher.Magazine += observerB.RecieveMagazine;
//出版社印刷本月的《故事会》
publisher.PublishingMagazine();
Console.ReadLine();
}
}
//读者
class Observer
{
public Observer(string _name)
{
name = _name;
}
public string name;
//接受信息的函数要与Event的格式保持一致,输入一个object对象和Event消息类
public void RecieveMagazine(object sender, Publisher.MagazineMessage magazineMessage)
{
Console.WriteLine("{0}收到了{1}, 仔细阅读了一番。", this.name, magazineMessage.Message);
}
}
//出版社
class Publisher
{
//事件传递的消息必须封装到一个类中,该类必须继承EventArgs类
public class MagazineMessage : EventArgs
{
public MagazineMessage(string mes)
{
message = mes;
}
private string message;
public string Message { get => message; set => message = value; }
}
public Publisher(string megName)
{
magazineName = megName;
}
public string magazineName;
//定义Event
public event EventHandler<MagazineMessage> Magazine;
//EventHandler<MagazineMessage> 的原型是一个泛型委托:delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)
//调用Event,Event只能在自己定义类中被触发调用
public void PublishingMagazine()
{
//与Delegate一样,此处必须判断event对象是否为空,调用空的Event/Delegate对象会引发异常
if (Magazine != null)
{
Magazine(this, new MagazineMessage(magazineName));
}
}
}
}
什么时候用委托?什么时候用事件?(个人观点)
由上可知,从语法上,我们可以使用event关键字将任意委托转化为事件。那么简而言之,出于代码健壮性考虑,如果一个委托不需要在其定义的类之外进行触发,那就将其转化为事件,保证它不会在不可知的情况下被触发。
扫描二维码关注公众号,回复:
5723363 查看本文章
更为深层的关于委托和事件的理解,请看下列参考资料。
参考资料
- 《C#本质论》
- C# 事件浅析
- 大白话系列之C#委托与事件讲解 系列