1、委托Delegate
首先说一下delegate委托,委托是将方法作为参数进行传递。
cs
// 定义了一个委托类型
public delegate void MyDelegate(int num);
// 定义了一个啥也不干的委托实例
public MyDelegate m_delegate = _ => {};
// 定义了一个和委托相同格式的方法
public void MyFun(int num){
Console.WriteLine(num);
};
// 添加新的委托
m_delegate += MyFun;
// 执行任务
m_delegate(2);
2、基于委托的发布/订阅模式
基于上例,对于如下的操作:
m_delegate += MyFun1;
m_delegate += MyFun2;
....
m_delegate(1);
这是一个委托的多播操作,一连串的任务会一起执行。
此时换一个思维,假如把+=操作看成是订阅操作。m_delegate(1)是发布,1可以看做是一个message,可以是int,也可以是string。这样就构成了一个发布/订阅模式。
当m_delegate(1)发布执行时,委托的多播会让所有订阅函数执行。
所以订阅者只需要把委托格式的函数告诉发布者(+=注册进去),当发布者发布时,所有的订阅者就相当于收到了消息。
下面写一个基于委托的发布/订阅模式:
cs
#define DEBUG3
using System;
using System.Diagnostics;
namespace ConsoleApp1
{
public delegate void SayGate(int num);
public class Publisher
{
// 定义委托
public SayGate SayGateHandler;
// 触发任务
public void OnTrigger(int num)
{
// 触发任务
SayGateHandler(num);
}
}
public class Subscriber
{
public void Subscribe(Publisher publisher)
{
// 订阅
publisher.SayGateHandler += SayHello;
}
public void SayHello(int num)
{
Console.WriteLine("subscribe1: " + num);
}
}
public class Subscriber2
{
public void Subscribe(Publisher publisher)
{
// 订阅
publisher.SayGateHandler += SayHello;
}
public void SayHello(int num)
{
Console.WriteLine("subscribe2: " + num);
}
}
internal class Program
{
static void Main(string[] args) {
Publisher publisher = new Publisher();
Subscriber subscriber = new Subscriber();
subscriber.Subscribe(publisher);
Subscriber2 subscriber2 = new Subscriber2();
subscriber2.Subscribe(publisher);
Console.ReadLine();
publisher.OnTrigger(1);
Console.ReadLine();
}
}
}
执行后会显示:
subscribe1: 1
subscribe2: 1
3、存在的缺陷
假如有个订阅者干了坏事,把"+="写成了"=",比如:
此时执行后会显示:
subscribe2: 1
即只显示了一条。
4、事件Event
针对上面的缺陷,需要引入一种机制限制订阅者的行为。
在C#中,event是一种特殊的委托,特殊之处为:订阅者只能通过"+="和"-="订阅或取消。发布者仍然可以进行"="操作。
当将SayGateHandler定义为event之后,订阅者2"="方式的订阅提示失败。
错误信息为:事件"Publisher.SayGateEvent"只能出现在+=或-=的左边(从类型"Publisher"中使用时除外)
新程序如下:
cs
namespace ConsoleApp1
{
public delegate void SayGate(int num);
public class Publisher
{
// 定义委托
public event SayGate SayGateHandler;
// 触发任务
public void OnTrigger(int num)
{
// 触发任务
SayGateHandler(num);
}
}
public class Subscriber
{
public void Subscribe(Publisher publisher)
{
// 订阅
publisher.SayGateHandler += SayHello;
}
public void SayHello(int num)
{
Console.WriteLine("subscribe1: " + num);
}
}
public class Subscriber2
{
public void Subscribe(Publisher publisher)
{
// 订阅
publisher.SayGateHandler += SayHello;
}
public void SayHello(int num)
{
Console.WriteLine("subscribe2: " + num);
}
}
internal class Program
{
static void Main(string[] args) {
Publisher publisher = new Publisher();
Subscriber subscriber = new Subscriber();
subscriber.Subscribe(publisher);
Subscriber2 subscriber2 = new Subscriber2();
subscriber2.Subscribe(publisher);
Console.ReadLine();
publisher.OnTrigger(1);
Console.ReadLine();
}
}
}
5、存在的缺陷2
以上程序有2个订阅者,但是假如没有订阅者,此时发布后会怎么样?
将以上代码注释后做测试,报错信息如下:
此处报错的意思是:SayGameHandler没有实例化。
有两种解决方案:
1)发布者实例化一个空的委托,
public event SayGate SayGateHandler = _ => { };
2)触发事件时检测下是否实例化,
// 触发任务
SayGateHandler?.Invoke(num);
两种方法选择一种即可。
最后一个完整的Event示例如下:
cs
namespace ConsoleApp1
{
public delegate void SayGate(int num);
public class Publisher
{
// 定义委托
public event SayGate SayGateHandler;
// 触发任务
public void OnTrigger(int num)
{
// 触发任务
SayGateHandler?.Invoke(num);
}
}
public class Subscriber
{
public void Subscribe(Publisher publisher)
{
// 订阅
publisher.SayGateHandler += SayHello;
}
public void SayHello(int num)
{
Console.WriteLine("subscribe1: " + num);
}
}
public class Subscriber2
{
public void Subscribe(Publisher publisher)
{
// 订阅
publisher.SayGateHandler += SayHello;
}
public void SayHello(int num)
{
Console.WriteLine("subscribe2: " + num);
}
}
internal class Program
{
static void Main(string[] args) {
Publisher publisher = new Publisher();
Subscriber subscriber = new Subscriber();
subscriber.Subscribe(publisher);
Subscriber2 subscriber2 = new Subscriber2();
subscriber2.Subscribe(publisher);
Console.ReadLine();
publisher.OnTrigger(1);
Console.ReadLine();
}
}
}