事件类似于异常,因为它们都由对象引发(抛出),我们可以提供代码来处理事件。而相应事件则必须先订阅(subscribe)它们。订阅一个事件的含义是提供代码,在事件发生时执行这些代码,这些代码称为事件处理程序。(C#的事件可以近似看作C++中的消息,C#中的事件处理程序对应于C++中的消息响应函数)。
单个事件可提供多个处理程序订阅,在该事件发生时,这些处理程序都会被调用。其中,包括引发该事件的对象所在的类中的事件处理程序,也可能是其它类中的事件处理程序。
欲掌握事件和事件处理程序方面的知识,必须先对委托有一定的了解。
1. 委托(delegate)
委托(delegate)是一种可以把引用存储为函数的类型。委托在事件和事件处理方面具有很重要的用途。
1.1 委托的声明和使用
委托的声明非常类似于函数,但不带函数体,且使用delegate关键字。委托的声明指定了一个返回类型和一个参数列表。
在定义了委托之后,就可以声明该委托类型的变量。接着把这个变量初始化为与委托有相同返回类型和参数列表的函数引用。之后,就可以使用委托变量调用这个函数,就像该变量是一个函数一样。还可以把委托变量作为一个参数传递给一个函数,这样,该函数就可以使用委托调用它引用的任何函数,且在运行之前无需知道调用的是哪个函数。
如下面代码所示,这是一个将委托变量作为参数传递的示例。首先定义了一个参数和返回类型都为double的ProcessDelegate委托,另外又定义了两个与委托有相同返回类型和参数列表的函数Multiple和Divide。在Main函数中使用新的委托类型声明了一个委托变量process,后将Multiple函数引用赋给process委托变量。注,必须使用new关键字创建一个委托。
<span style="font-size:14px;">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Delegate
{
class Program
{
//定义一个委托
delegate double ProcessDelegate(double param1, double param2);
//Multiply函数
static double Multiply(double param1, double param2)
{
return param1 * param2;
}
//Divide函数
static double Divide(double param1, double param2)
{
return param1 / param2;
}
static void Main(string[] args)
{
//声明一个ProcessDelegate委托类型的变量
ProcessDelegate process;
//初始化委托为Multiply函数的引用
process = new ProcessDelegate(Multiply);
//通过委托调用委托所引用的函数
Console.WriteLine("Result is {0}", process(10, 2));
//改变委托的为Divide函数的引用
process = new ProcessDelegate(Divide);
Console.WriteLine("Result is {0}", process(10, 2));
Console.ReadKey();
}
}
}</span>
其运行结果为:
委托有许多用途,但它们的大多数用途主要与事件处理有关。
2.事件处理程序
事件处理程序本身都是简单的方法。对事件处理方法定义的唯一限制是它必须匹配于事件所要求的返回类型和参数。这个限制是事件定义的一部分,由一个委托(delegate)指定。
2.1 事件处理示例
我们用事件处理程序来订阅事件,该方法的返回类型和参数应该匹配事件指定的委托。示例如以下代码所示,这是一个响应定时器Elapsed事件的示例程序。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
namespace Message
{
class Program
{
static long count = 0;
//与ElapsedEventHandler委托类型相匹配的函数Counter
static void Counter(object source, ElapsedEventArgs e)
{
count++;
Console.WriteLine("{0} seconds have passed!", count);
}
static void Main(string[] args)
{
//定义一个定时器,每隔1000毫秒触发一次Elapsed事件
Timer myTimer = new Timer(1000);
//Counter处理程序与Elapsed事件相关联
myTimer.Elapsed += new ElapsedEventHandler(Counter);
//启动定时器
myTimer.Start();
Console.ReadKey();
//关闭定时器
myTimer.Stop();
}
}
}
C#定时器Timer对象有一个Elapsed事件,这个事件要求事件处理程序必须匹配System.Timers.ElapsedEventHandler委托类型的返回类型和参数,该委托是.NET Framework中定义的标准委托之一,指定的返回类型和参数为:
void functionName(objectsource, ElapsedEventArgs e);
第一个参数source是Timer对象本身的引用,第二个参数ElapsedEventArgs由事件传送的参数。
我们在代码中定义了一个参数列表和返回值与ElapsedEventHandler委托类型相匹配的函数Counter。并将Counter处理程序与Elapsed事件相关联。运行结果如下图所示:
2.2 定义事件
在定义事件前,必须先定义一个委托类型,以用于该事件。这个委托类型制定了事件处理方法必须拥有的返回类型和参数。
下面示例代码中定义的委托类型为MessageHandler,返回类型为void,带有一个string类型的参数。
public delegate voidMessageHandler(string messageText);
使用event关键字声明事件,并指定要使用的委托类型,下面代码将事件命名为MyMessage。
public event MessageHandlerMyMessage;
声明事件后就可以引发事件,事件的引发只是按名称来调用它,就好像它是一个返回类型和参数与委托指定的方法一样。
MyMessage(str);
下面示例代码,定义了一个名为Connections的类,在类中定义了一个名为MessageHandle的委托类型,定义了名为MyMessage的事件,事件的处理程序类型为MessageHandle委托类型。在Connections类中,当计数器count值为5的倍数时,就会引发MyMessage事件。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
namespace DefineDelegate
{
class Connections
{
//定义一个委托类型
public delegate void MessageHandler(string messageText);
//定义事件
public event MessageHandler MyMessage;
public Timer MyTimer;
private static long count = 0;
public Connections()
{
MyTimer = new Timer(1000);
//注册Elapsed的事件处理程序为Counter
MyTimer.Elapsed += new ElapsedEventHandler(Counter);
MyTimer.Start();
}
~Connections()
{
MyTimer.Stop();
}
public void Counter(object sender, ElapsedEventArgs e)
{
count++;
if (count % 5 == 0 && MyMessage != null)
{
string str = "the number is " + count.ToString() + ", it is a muliple of 5!";
MyMessage(str);
}
}
}
}
其中,表达式“MyMessage != null”使用了委托语法,其含义是“该事件是否有订阅者”,如果没有订阅者,MyMessage就是null,就不会引发事件。只有在MyMessage!= null的情况下,才会引发一个事件。
如下面代码所示,在Main函数中定义了一个Connections对象,对象的MyMessage事件的事件处理程序为我们自定义的TheResult函数。定义的TheResult函数和在Connetions类中定义的事件处理程序类型MessageHandler一致。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
namespace DefineDelegate
{
class Program
{
static void Main(string[] args)
{
Connections ConnectObj = new Connections();
//注册事件的处理程序
ConnectObj.MyMessage += new Connections.MessageHandler(TheResult);
Console.ReadKey();
}
static void TheResult(string msg)
{
Console.WriteLine(msg);
}
}
}
程序运行后的结果为: