C#事件

2015-08-05  本文已影响866人  天堂迈舞

事件

事件含义

事件由对象引发,通过我们提供的代码来处理。一个事件我们必须订阅(Subscribe)他们,订阅一个事件的含义就是提供代码,在这个事件发生时执行这些代码,这些代码称为事件处理程序
一个事件可以被多个事件处理程序订阅,在这个事件发生时,这些处理程序都会被执行。事件处理程序可以在该事件的对象所处的类中,也可以在其他类中。
事件处理程序本身就是一个普通的方法,对这个方法的唯一限制是:必须匹配事件所要求的返回类型和参数,这个限制是事件定义的一部分,由一个委托指定。

处理事件

要处理事件,需要提供一个事件处理方法来订阅事件,方法的返回类型和参数必须匹配事件指定的委托。下面使用一个简单的计时器对象来引发事件,调用一个处理方法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;

namespace eve
{
    class Program
    {
        static int counter = 0;

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

        static void Main(string[] args)
        {
            Timer myTimer = new Timer(100);
            myTimer.Elapsed += WriteChar;
            // myTimer.Elapsed += WriteChar2;
            //myTimer.Elapsed += new ElapsedEventHandler(WriteChar);
          //  myTimer.Elapsed += (sender, eventArgs) =>
           // {
           //     Console.Write(displayString[counter++ % displayString.Length]);
          //  };
            myTimer.Start();
            System.Threading.Thread.Sleep(2000);
            Console.ReadKey();
        }

        static void WriteChar(object source, ElapsedEventArgs e)
        {
            Console.Write(displayString[counter++ % displayString.Length]);
        }
        static void WriteChar2(object source, ElapsedEventArgs e)
        {
            Console.Write(counter);
        }
    }
}

这个示例运行后将会打印字符,一个一个打印。那么我来解释一下上面的栗子。

Timer 是一个定时器,每隔一段时间会触发一个事件,这个时间在构造函数里给出,这里是100ms。要引发事件,首先要把这个定时器跑起来,就是myTimer.Start();
这个Timer呢有一个事件叫做Elapsed,我们就要用一个方法去订阅它。条件就是必须匹配System.Timers.ElapsedEventHandler这个委托类型的返回类型和参数。这个委托来自.Net Framework标准委托。它指定了如下的返回类型和参数。
void <MethodName> (Object source, ElapsedEventArgs e);
其中的source参数是Timer对象本身的引用,ElapsedEventArgs是对象的一个实例。后面继续介绍。
在上面给出的代码中有满足这个委托返回类型和参数列表的方法

static void WriteChar(object source, ElapsedEventArgs e)
{
        Console.Write(displayString[counter++ % displayString.Length]);

}

先不管后面那个WriteChar2。
里面的方法在屏幕上一次输出字符串中的字符。
好了,事件有了,对应的方法有了,剩下的工作就是去订阅这个事件。
使用`+=`运算符,给事件添加一个处理程序,形式是使用事件处理方法初始化一个新的**委托实例**。但是方法不止一种:
1. `myTimer.Elapsed += new ElapsedEventHandler(WriteChar);`
2. `Timer.Elapsed += WriteChar;`
上面两个方式都是可以的,使用第2种编译器会根据使用的上下文来指定委托类型。坏处就是降低可读性,你不能一下就知道这个委托类型是什么。
其实还有方法:
**Lambda表达式**

myTimer.Elapsed += (sender, eventArgs) =>
{
Console.Write(displayString[counter++ % displayString.Length]);
};

或者 **匿名方法**

myTimer.Elapsed += delegate(object sender, MessageArrivedEventArgs eventArgs)
{
Console.Write(displayString[counter++ % displayString.Length]);
};

上面的`(object sender, MessageArrivedEventArgs eventArgs)`也是可以省略的。

至此,所有的工作都完成啦。
再理一遍:定时器每隔100ms引发一个事件,这个事件被我们的方法订阅,事件触发后去找那个订阅的处理程序,执行那个方法,在屏幕上输出内容。

创建事件

上面使用了Timer自带的事件,我们也可以创建我们自己的事件。这里是一个即时消息传送程序,创建一个Connection对象,这个对象引发由Display对象处理的事件。
首先定义一个委托类型,你一定知道用它干嘛。
public delegate void MessageHandler(string messageText);

这个委托类型称为MessageHandler,有一个void的方法签名,有一个string 参数。

Connection这个类里定义一个事件

public event MessageHandler MessageArrived

给事件命名,这里使用MessageArrived,在声明时,使用event关键字,并指定要使用的委托类型MessageHandler。这样声明以后,就可以引发它。方法是按名称来调用它。例如:
MessageArrived("This is a message.");
为什么是这样呢?
MessageArrived这是一个委托实例,按照委托的方法传参。
要是定义的委托不需要参数,那就这样MessageArrived();
要是参数多就要用更多的代码区实现。

先上代码吧。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;

namespace eve
{
   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 static Random random = new Random();

      private void CheckForMessage(object source, ElapsedEventArgs e)
      {
         Console.WriteLine("Checking for new messages.");
         if ((random.Next(9) == 0) && (MessageArrived != null))
         {
            MessageArrived("Hello Mum!");
         }
      }
   }
}

if ((random.Next(9) == 0) && (MessageArrived != null))这里得到一个随机数,得到0时引发事件,后面的MessageArrived!=null检查事件是否有被订阅,如果没有被订阅那么MessageArrived=null不会引发事件。

下面是Display

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace eve
{
   public class Display
   {
      public void DisplayMessage(string message)
      {
         Console.WriteLine("Message arrived: {0}", message);
      }
   }
}

其中的public void DisplayMessage(string message)满足上面定义的委托返回类型和参数,可以用它来订阅事件MessageArrived

主程序是这样的

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace eve
{
   class Program
   {
      static void Main(string[] args)
      {
         Connection myConnection = new Connection();
         Display myDisplay = new Display();
         myConnection.MessageArrived +=
                 new MessageHandler(myDisplay.DisplayMessage);
         myConnection.Connect();
         System.Threading.Thread.Sleep(200);
         Console.ReadKey();
      }
   }
}

下面是运行结果

运行结果

多用途的事件处理程序

前面使用的Timer.Elapsed事件的委托包含了事件处理程序中常见的两类参数

 public class MessageArrivedEventArgs : EventArgs
   {
      private string message;

      public string Message
      {
         get
         {
            return message;
         }
      }

      public MessageArrivedEventArgs()
      {
         message = "No message sent.";
      }

      public MessageArrivedEventArgs(string newMessage)
      {
         message = newMessage;
      }
   }

修改Connection

public class Connection
   {
      public event EventHandler<MessageArrivedEventArgs> MessageArrived;
      private Timer pollTimer;

      public string Name { get; set; }

      public Connection()
      {
         pollTimer = new Timer(100);
         pollTimer.Elapsed += new ElapsedEventHandler(CheckForMessage);
      }

      public void Connect()
      {
         pollTimer.Start();
      }

      public void Disconnect()
      {
         pollTimer.Stop();
      }

      private static Random random = new Random();

      private void CheckForMessage(object source, ElapsedEventArgs e)
      {
         Console.WriteLine("Checking for new messages.");
         if ((random.Next(9) == 0) && (MessageArrived != null))
         {
            MessageArrived(this, new MessageArrivedEventArgs("Hello Mum!"));
         }
      }
   }

修改Display

public class Display
   {
      public void DisplayMessage(object source, MessageArrivedEventArgs e)
      {
         Console.WriteLine("Message arrived from: {0}",
                           ((Connection)source).Name);
         Console.WriteLine("Message Text: {0}", e.Message);
      }
   }

主程序修改为

 static void Main(string[] args)
      {
         Connection myConnection1 = new Connection();
         myConnection1.Name = "First connection.";
         Connection myConnection2 = new Connection();
         myConnection2.Name = "Second connection.";
         Display myDisplay = new Display();
         myConnection1.MessageArrived += myDisplay.DisplayMessage;
         myConnection2.MessageArrived += myDisplay.DisplayMessage;
         myConnection1.Connect();
         myConnection2.Connect();
         System.Threading.Thread.Sleep(200);
         Console.ReadKey();
      }

发送一个引发事件的对象引用,把这个对象作为事件处理程序的一个参数,就可以为不同的对象定制处理程序的响应。可以利用这个对象访问源对象,包括它的属性。
通过发送包含在派生于System.EventArgs类中的参数,就可以将其他信息作为参数提供。这些参数也得益于多态性。为MessageArrived事件定义一个处理程序:

 public void DisplayMessage(object source, MessageArrivedEventArgs e)
      {
         Console.WriteLine("Message arrived from: {0}",
                           ((Connection)source).Name);
         Console.WriteLine("Message Text: {0}", e.Message);
      }

这个处理程序可以处理不限于Timers.Elapsed的事件。但是要修改里面的代码,并注意检查null值。

EventHandler和泛型EventHandler<T>类型

这里有一个规范,事件处理程序的返回类型应为void,参数应该是两个,第一个是object,第二个参数是派生于System.EventArgs。为此.Net 提供了两个委托类型EventHandlerEventHandle <T>,以便定义事件。上面的例子中,删去了自己定义的委托转而使用EventHandle<T>泛型委托。

public event EventHandler<MessageArrivedEventArgs> MessageArrived;

上面提到的两个委托类型EventHandlerEventHandle <T>,他们的返回类型都是void,参数列表

public delegate void EventHandler(object sender, System.EventArgs e)
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)

这显然简化了我们的代码。一般,在定义事件时,最好使用这些委托类型。如果事件不需要事件实参数据,仍然可以用EventHandler委托类型,但是要传递EventArgs.Empty作为实参。
也可以为事件提供返回类型,但这有一个问题。引发给定的事件,可能会调用多个事件处理程序。如果这些处理程序都返回一个值,那么我们只能得到最后一个订阅该事件的处理程序的返回值。有些情况下可能是有用的,但最好使用void作为返回类型。

匿名方法

前面提到过匿名方法,是为用作委托目的而创建的。要创建匿名方法,使用以下代码:

delegate(parameters){
    //具体代码
}

其中parameters是一个参数列表,这些参数列表匹配正在实例化的委托类型,由匿名方法使用,例如:

delegate(Connection source, MessageArrivedEventArgs e){
> Console.WriteLine("Message arrived from: {0}",
                           ((Connection)source).Name);
         Console.WriteLine("Message Text: {0}", e.Message);
> };

使用下面的代码可以完全绕过Display.DisplayMessage()方法:

myConnection1.MessageArrived += delegate(Connection source, MessageArrivedEventArgs e){
Console.WriteLine("Message arrived from: {0}",
                           ((Connection)source).Name);
         Console.WriteLine("Message Text: {0}", e.Message);
         }


示例代码和部分内容来自《C#入门经典(第六版)》

上一篇下一篇

猜你喜欢

热点阅读