第十四章 事件
-
发布者和订阅者:发布者类定义了一系列其他类感兴趣的事件,订阅者需要注册,才能在事件被触发时得到通知。订阅者通过向事件提供方法完成注册,事件触发时,会依次调用注册到事件中的方法。
-
回调方法:订阅者注册到事件中的方法,也叫事件处理程序,毕竟是为处理事件而调用的代码。
-
发布者:发布某个事件的类或结构,当事件被触发时其他类可以得到通知。当声明为public时,表示发布了事件。
-
订阅者:注册并在事件被触发时得到通知的类或结构。
-
事件处理程序:由订阅者注册到事件中的方法,在发布者触发事件时执行。事件处理程序可以定义在事件所在的类或结构中,也可以定义在不同的类或结构中。
-
触发事件:当事件被触发时,通过委托依次调用调用列表中的方法。
-
事件包含一个私有内部委托,事件提供对它的私有控制委托的结构化访问,你不能直接访问委托;事件可用的操作要比委托少,只允许增加、删除和调用事件处理程序
-
使用事件需要五个源代码组件:
(1)委托类型声明:事件和事件处理程序必须有共同的参数列表和返回类型,它们通过委托类型来描述。(2)事件声明:发布者类必须声明一个订阅者类可以注册的事件成员。
(3)事件注册:订阅者必须订阅事件才能在它被触发时得到通知。
(4)声明事件处理程序:订阅者类中会在事件触发时执行的方法声明,不一定是显示命名的方法,可以是匿名方法 或Lambda表达式。
(5)触发事件的代码:发布者类中“触发”事件并导致调用注册的所有事件处理程序的代码
-
声明事件:发布者类必须提供事件对象,创建事件需要委托类型和名字。注意,事件声明在一个类型中;它需要委托类型的名称,任何附加到事件的处理程序都必须与委托类型的参数列表和返回类型匹配;声明为public,这样其他类和结构可以在它上面注册事件处理程序;不能使用对象创建表达式来创建它的对象。如果要声明多个事件,事件之间用逗号隔开。
class Incrementer { public event EventHandler CountedADozen;}
event 为关键字,EventHandler 为委托类型,CountedADozen为事件名。 -
和方法、结构一样,事件是类或结构的成员,所以,我们不能在一段可执行代码中声明事件,它必须在类或结构中声明;事件成员被隐式自动化为null。
-
事件声明需要委托类型的名字,我们可以声明一个委托类型或使用已经存在的,如果我们声明一个委托类型,它必须指定事件保存的参数列表和返回类型。
-
订阅事件:订阅者向事件添加事件处理程序,对于一个要添加到事件的事件处理程序而言,它必须拥有与事件的委托相同的返回类型和签名;使用+=运算符为事件增加事件处理程序;事件处理程序可以是实例方法的名称、静态方法的名称、匿名方法以及lambda表达式。
incrementer.CountedADozen += IncrementDozensCount; //方法引用形式
incrementer.CountedADozen += ClassB.CounterHandlerB; //方法引用形式
mc.CountedADozen += new EventHandler(cc.CounterHandlerC); //委托形式
incrementer.CountedADozen += () =>DozensCount++; //Lambda表达式
incrementer.CountedADozen += delegate{ DozensCount++; }; //匿名方法
- 触发事件:我们需要在合适的时候有代码来做这件事。注意,在触发事件之前需要与null比较,从而查看是否包含事件处理程序,如果没有,则不执行程序;触发事件的语法与调用方法一样,使用事件名称后跟参数列表、参数列表必须与事件的委托类型相匹配。
if(CountedADozen !=null) //确认方法可行
CountedADozen(source,args); //触发事件
- 发布者类声明:
class Incrementer
{
public event EventHandler CountedADozen; //声明事件
void DoCount(object source,EventArgs args)
{
for(int i =1;i<100;i++)
if(i%12 ==0)
if(CountedADozen != null) //确认有方法可以执行
CountedADozen(source,args); //触发事件
}
}
- 完整示例:
delegate void Handler(); //声明委托
//发布者类
class Incrementer
{
public event Handler CountedADozen; //创建事件并发布
public void DoCount()
{
for(int i =0;i<100;i++)
if(i%12 == 0 && CountedADozen !=null)
CountedADozen(); //每增加12个计数触发事件一次
}
}
//订阅者类
class Dozens
{
public int DozensCount { get;private set; }
public Dozens(Incrementer incrementer)
{
DozensCounts = 0;
incrementer.CountedADozen += IncrementerDozensCount; 订阅事件
}
void IncrementerDozensCount() //声明事件处理程序
{
DozensCount++;
}
}
class Program
{
static void Main()
{
Incrementer incrementer = new Incrementer();
Dozens dozensCount = new Dozens(incrementer);
incrementer.DoCount();
Console.WriteLine("Number of dozens = {0}",dozensCount.DozensCount);
}
}
结果是:Number of dozens = 8
GUI编程是事件驱动的,程序在执行的任何时候都可以被事件打断,程序需要处理事件后继续其他事情,C#事件运用在程序事件的异步处理中会恰到好处。
- 标准事件的用法:对于事件的使用,.NET框架提供了一个标准模式。时间使用的标准模式的根本在于System命名空间生命的EventHandler委托类型。格式为
public delegate void EventHandler(object sender,EventArgs e);
其中,第一个参数用于保存触发事件对象的引用,由于是object类型,可以与任何类型相匹配;第二个参数用于保存状态信息,用来指示什么类型适用于应用程序。 - EventArgs设计为不传递任何数据,它用于不需要传递数据的事件处理程序,如果希望传递数据,必须声明一个派生自EventArgs的类,使用合适的字段保存需要传递的数据。
- 尽管EventArgs类不传递数据,但它是使用EventHandler委托模式的重要部分,不管参数的实际类型是什么,object和EventArgs始终是基类,这样Event Handler提供一个对所有事件和所有事件处理器都通用的签名,只允许两个参数,而不是各自都有两个不同签名。
- 示例:
//发布者类
class Incrementer
{
public event EventHandler CountedADozen; //使用系统定义的EventHandler委托
public void DoCount()
{
for(int i =0;i<100;i++)
if(i%12 == 0 && CountedADozen !=null)//每增加12个计数触发事件一次
CountedADozen(this,null); //触发事件时使用EventHandler
}
}
//订阅者类
class Dozens
{
public int DozensCount { get;private set; }
public Dozens(Incrementer incrementer)
{
DozensCounts = 0;
incrementer.CountedADozen += IncrementerDozensCount; 订阅事件
}
void IncrementerDozensCount(object source,EventArgs e) //事件处理程序的签名必须与委托的签名相匹配
{
DozensCount++;
}
}
class Program
{
static void Main()
{
Incrementer incrementer = new Incrementer();
Dozens dozensCount = new Dozens(incrementer);
incrementer.DoCount();
Console.WriteLine("Number of dozens = {0}",dozensCount.DozensCount);
}
}
- 通过扩展EventArgs来传递数据,需要声明一个派生自EventArgs的自定义类,它可以保存我们需要传入的数据,类的名称应当以EventArgs结尾。有人一个自定义的类,可以对事件处理程序的第二个参数传递数据,你需要使用新自定义的委托类型,要获得该类,可以使用泛型委托EventHandler<>,将自定义类放在尖括号内。
- 示例;
public class IncrementerEventArgs : EventArgs //自定义类派生自EventArgs
{
public int IterationCount{get;set;} //存储数据
}
class Incrementer
{
public event EventHandler<IncrementerEventArgs > CountedADozen;
public void DoCount() //自定义类对象
{
IncrementerEventArgs args = new IncrementerEventArgs ();
for(int i =0;i<100;i++)
if(i%12 == 0 && CountedADozen !=null) //每增加12个计数触发事件一次
{
args.IterationCount = i;
CountedADozen(this,args); //在触发事件时传递参数
}
}
}
class Dozens
{
public int DozensCount{get;private set;}
public Dozens(Incrementer incrementer)
{
DozensCounts = 0;
incrementer.CountedADozen += IncrementerDozensCount; 订阅事件
}
void IncrementerDozensCount(object source, IncrementerEventArgs e) //事件处理程序的签名必须与委托的签名相匹配
{
Console.WriteLine("Incremented at iteration : {0} in {1}",e.IterationCount,source.ToString());
DozensCount++;
}
}
class Program
{
static void Main()
{
Incrementer incrementer = new Incrementer();
Dozens dozensCount = new Dozens(incrementer);
incrementer.DoCount();
Console.WriteLine("Number of dozens = {0}",dozensCount.DozensCount);
}
}
- 如果要移除事件处理程序,可以使用 -= 运算符。
- 事件访问器:+=和-=是事件允许的唯一运算符,这些运算符有预定义的行为要改变运算符的操作,可以为事件定义事件访问器。有两个访问器add和remove,声明事件的访问器和声明一个属性差不多
public event EventHandler CountedADozen
{
add
{
... //执行+=运算符
}
remove
{
... //执行-=运算符
}
}
声明类事件访问器之后,事件不包含任何内嵌委托对象,我们必须声明自己的机制来存储和移除事件注册的方法。事件处理器表现为void方法,不能使用包含返回值return的语句。