发布者/订阅者模式
当一个特定的程序事件发生时,程序的其他部分可以得到该事件已经发生的通知。发布者/订阅者模式可以满足这种需求。这种模式中,发布者类定义了一系列程序的其他部分可能感兴趣的事件,其他类可以注册,以便在这些事件发生时发布者可以通知他们,这些订阅者类通过向发布者提供一个方法来注册以获取通知,当事件发生时,发布者触发事件,然后执行订阅者提交的所有事件。由订阅者提供的方法称为回调方法,因为发布者通过执行这些方法来往回调用订阅者的方法,还可以将它们称为事件处理程序。
发布者:
发布某个事件的类或结构,其他类可以在该事件发生时得到通知。
订阅者:
注册并在事件发生时得到通知的类或结构。|
事件处理程序:
由订阅者注册到事件的方法,在发布者触发事件时执行,事件处理程序方法可以定义在事件所在的类或结构中,也可以定义在不同的类或结构中
触发事件:
当事件触发时,所有注册到它的方法都会被依次调用。
事件很多部分都与委托类似,实际上,事件就像是专门用于某种特殊用途的简单委托,如图所示,事件包含了一个私有委托。
1.事件提供了对它的私有控制委托的结构化访问,也就是说,你无法直接访问委托。
2.事件中可用的操作比委托要少,对于事件我们只可以添加、删除或调用事件处理程序。如图事件只有+=、-=,它们是事件唯一允许的操作(除了调用事件本身)
3.事件被触发时,它调用委托来依次调用调用列表中的方法。
使用事件时的5个源代码组件:
1.委托类型声明:
事件和事件处理程序必须有共同的签名和返回类型,它们通过委托类型进行描述
2.事件处理程序声明:
订阅者类中会在事件触发时执行的方法声明,它们不一定是有显式命名的方法,还可以是匿名方法或Lambda表达式。
3.事件声明:
发布者必须声明一个订阅者类可以注册的事件成员,当声明的事件为public时,称为发布了事件。
4.事件注册:
订阅者必须订阅事件才能在它被触发时得到通知
5.触发事件的代码:
发布者类中触发事件并导致调用注册的所有事件程序的代码。
声明事件
1.事件声明在一个类中,事件成员被隐式自动初始化为
null
2.它需要委托类型名称,任何附加到事件(如注册)的处理程序都必须与委托类型的签名和返回类型匹配。
3.它声明为
public
,这样其他类和结构可以在它上面注册事件处理程序
4.不能使用对象表达式(new)来创建它的对象
5.可以通过使用逗号分隔的列表在一个声明语句中声明一个以上的事件,也可以使用static修饰事件
class A
{
//EventHandler委托专门用于系统事件
public event EventHandler e1;
public event EventHandler e2, e3;
public static EventHandler e4;
}
使用事件
把事件声明和触发事件的代码放在了一起便有了如下的发布者类声明,在
触发事件之前和null进行比较,从而查看是否包含事件处理程序,如果事件时null,则表示没有,不能执行
,
但是这个判断只能在声明事件的类内部才可以使用!=进行判断,类外部则不能使用,因为类外部只能使用+=、-=进行事件的订阅和取消订阅
。
//声明委托类型
public delegate void Mydel();
//发布者
public class Publish
{
//创建事件并发布
public event Mydel MyEvent;
//触发代码
public void Trigger()
{
for(int i=0;i<100;i++)
{
if (i % 10 == 0 && MyEvent != null) //确认有方法可执行,MyEvent只有在本类中可以使用!=判断,类外只能用+=、-=
MyEvent(); //触发事件
}
}
}
//订阅者
public class Subscribe
{
public int i { get; set; } = 0;
public Subscribe(Publish p)
{
p.MyEvent += foo; //订阅事件
}
//事件处理程序
public void foo()
{
i++;
}
}
public class Program
{
static void Main(string[] args)
{
Publish p = new Publish();
Subscribe s = new Subscribe(p);
p.Trigger();
Console.WriteLine(s.i);
}
}
标准事件的用法
对于事件的使用,NET框架提供了一个标准模式,事件使用的标准模式的根本就是System命名空间声明的EventHandler委托类型,该委托类型的声明需注意:
(1)第一个参数用来保存触发对象的引用,由于是object类型的,所以可以匹配任何类型的实例
(2)第二个参数用来保存状态信息,指明什么类型适用于该应用程序,
EventArgs设计为不能传递任何数据
,
它用于不需要传递数据的事件处理程序时会被忽略
,
如果希望传递数据,则必须声明一个派生自EventArgs的类,使用合适的字段来保存需要传递的数据
,尽管EventArgs类实际上并不传递数据,但它是用EventHandler委托模式的重要部分,不管参数使用的实际类型是什么,object类和EventArgs总是基类,这样EventHandler就能提供一个对所有事件和事件处理器都通用的签名。
(3)返回类型为void
public delegate void EventHandler(object sender, EventArgs e);
通过扩展EventArgs来传递数据
首先定义一个继承自EventArgs的自定义类,类名最好以EventArgs为结尾,然后使用新自定义的类的委托类型,使用EventHandler<>模板,将自定义填入模板中,即可生成对应的委托类型,然后声明该事件。
//自定义参数类
public class PublishEventArgs : EventArgs
{
public int Num { get; set; } //存储整数
}
//发布者
public class Publish
{
//创建事件并发布
public event EventHandler<PublishEventArgs> MyEvent;
//触发代码
public void Trigger()
{
PublishEventArgs args = new PublishEventArgs();
for(int i=0;i<100;i++)
{
if (i % 10 == 0 && MyEvent != null) //确认有方法可执行,MyEvent只有在本类中可以使用!=判断,类外只能用+=、-=
{
args.Num = i;
MyEvent(this, args); //触发事件
}
}
}
}
//订阅者
public class Subscribe
{
public int i { get; set; } = 0;
public Subscribe(Publish p)
{
p.MyEvent += foo; //订阅事件
}
//事件处理程序
public void foo(object source, PublishEventArgs e)
{
Console.WriteLine("{0} {1}", e.Num, source.ToString());
i++;
}
}
public class Program
{
static void Main(string[] args)
{
Publish p = new Publish();
Subscribe s = new Subscribe(p);
p.Trigger();
Console.WriteLine(s.i);
}
}