代码改变世界

C#学习笔记(十一):事件基础

2006-02-05 16:34  努力学习的小熊  阅读(1432)  评论(0编辑  收藏  举报

事件类似于异常,因为它们都由对象引发。但是它们也有几个重要的区别:其中最重要的区别就是没有处理异常中try...catch类似的结构来处理事件,而是必须订阅它们。订阅一个事件的含义是提供代码,在事件发生时执行这些代码,它们称为事件处理程序。

事件处理程序本身都是简单的函数。对事件处理函数的唯一限制是它必须匹配于事件所要求的签名(返回类型和参数)。这个签名是事件定义的一部分,由一个委托指定

处理过程:

首先,应用程序创建一个可以引发事件的对象。例如,假定应用程序是一个即时信息传送应用程序,它创建的对象表示一个远程用户的连接。当接收到通过该连接从远程用户传送来的信息时,这个连接对象会引发一个事件。

接着,应用程序订阅事件。为此,即时消息传送应用程序将定义一个函数,该函数可以与事件指定的委托类型一起使用,把这个函数的一个引用传送给事件,而事件的处理函数可以是另一个对象的方法,假定是表示显示设备的对象,当接收到信息时,该方法将显示即时信息。

引发事件后,就通知订阅器。当接收到通过连接对象传送过来的即时消息时,就调用显示设备上的事件处理方法。因为我们使用的是一个标准方法,所以引发事件的对象可以通过参数传送任何相关的信息,这样就大大增加了事件的通用性。

看下面这个例子:

using System;

using System.Timers;

 

namespace Ch12Ex01

{

    /// <summary>

    /// Class1 的摘要说明。

    /// </summary>

    class Class1

    {

        static int counter = 0;

 

        static string displayString = "This string will appear one letter at a time.";

 

        [STAThread]

        static void Main(string[] args)

        {

            Timer myTimer = new Timer(100);

            myTimer.Elapsed += new ElapsedEventHandler(WriteChar);

            myTimer.Start();

            Console.ReadLine();

        }

 

        private static void WriteChar(object sender, ElapsedEventArgs e)

        {

            if(counter>displayString.Length-1)

                return;

            Console.Write(displayString[counter++%displayString.Length]);

        }

    }

}

用于引发事件的对象是System.Timers.Timer类的一个实例。使用一个时间段来初始化该对象,当使用Start()方法启动Timer对象时,就引发一系列事件,根据指定的时间段来引发事件。

Timer对象有一个Elapsed事件,这个事件要求的事件处理程序签名是System.Timers. ElapsedEventHandler委托类型,该委托是在.NET Framework中定义的标准委托之一,用于与下述签名匹配的函数:

void functionName(object sender, ElapsedEventArgs e)

下一个任务是把这个处理程序与事件关联起来——即订阅它。可以使用+=运算符,给事件添加一个处理程序,其形式是用事件处理程序方法初始化的一个新委托实例:

myTimer.Elapsed += new ElapsedEventHandler(functionName)

 

下面是如何定义和使用自己的事件。

在定义事件前,必须先定一个委托类型,以用于该事件,这个委托类型指定了事件处理方法必须遵循的签名

public delegate void MessageHandler(string messageText);

这个委托类型称为MessageHandler,是void函数的签名,它有一个string参数。使用这个参数可以把Connection对象接收过来的即时消息发送给Display对象。

定义了委托后(或者定位现有合适的委托),就可以把事件本身定义为Connection类的一个成员。

    public class Connection

    {

        public event MessageHandler MessageArrived;

给事件命名(这里使用的名称为MessageArrived),用event关键字和要使用的委托类型(前面定义的MessageHandler委托类型)声明它。

以这种方式声明了事件后,就可以引发它,方法是按名称来调用它,就好像它是一个其签名是由委托指定的方法一样。例如使用下面的代码引发这个事件:

MessageArrived("This is a message.");

如果定义该委托不包含任何参数,就可以使用下面的代码:

MessageArrived();

订阅事件的类是Display,它包含一个方法DisplayMessage()

    public class Display

    {

        public void DisplayMessage(string message)

        {

            Console.WriteLine("Message arrived:{0}",message);

        }

    }

这个方法匹配于委托类型方法的签名(返回类型和参数),所以可以使用它响应MessageArrived事件。

 

在进行如下定义后

            Connection myConnection = new Connection();

            Display myDisplay = new Display();

            myConnection.MessageArrived += new MessageHandler(myDisplay.DisplayMessage);

我们就可以看出,为myConnection对象的MessageArrived事件进行初始化时,因为MessageArrived事件是用MessageHandler代理类型定义的(void返回值和string参数),而Display类下的DisplayMessage方法的定义与这个委托类型方法的签名一致,所以可以将这个事件引发后要执行的方法转到这个方法上来。

下面是整个程序用到的源代码:

Connection

using System;

using System.Timers;

 

namespace Ch12Ex02

{

    public delegate void MessageHandler(string messageText);

 

    public class Connection

    {

        public event MessageHandler MessageArrived;

 

        private Timer pollTimer;

 

        public Connection()

        {

            pollTimer = new Timer(100);

            pollTimer.Elapsed += new ElapsedEventHandler(CheckForMessage);

        }

 

        public void Connect()

        {

            pollTimer.Start();

        }

 

        public void Disconnect()

        {

            pollTimer.Stop();

        }

 

        private void CheckForMessage(object source, ElapsedEventArgs e)

        {

            Console.WriteLine("checking for new message.");

            Random random = new Random();

            if((random.Next(9) == 0) && (MessageArrived != null))

            {

                MessageArrived("Hello Mum!");

            }

        }

    }

}

Display

using System;

 

namespace Ch12Ex02

{

    /// <summary>

    /// Display 的摘要说明。

    /// </summary>

    public class Display

    {

        public void DisplayMessage(string message)

        {

            Console.WriteLine("Message arrived:{0}",message);

        }

    }

}

Class1

using System;

 

namespace Ch12Ex02

{

    /// <summary>

    /// Class1 的摘要说明。

    /// </summary>

    class Class1

    {

        /// <summary>

        /// 应用程序的主入口点。

        /// </summary>

        [STAThread]

        static void Main(string[] args)

        {

            Connection myConnection = new Connection();

            Display myDisplay = new Display();

            myConnection.MessageArrived += new MessageHandler(myDisplay.DisplayMessage);

            myConnection.Connect();

            Console.ReadLine();

        }

    }

}