事件
事件含義
事件由對象引發,通過我們提供的代碼來處理。一個事件我們必須訂閱(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
事件的委托包含了事件處理程序中常見的兩類參數
-
object source
引發事件的對象的引用 -
ElapsedEventArgs e
由事件傳送的參數
在事件中使用object
類型的原因是,我們常常要為由不同對象引發的幾個相同事件使用同一個事件處理程序,但仍要指定是哪個對象生成了事件。
下面擴展上面的示例。
添加一個新類MessageArrivedEventArgs
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
提供了兩個委托類型EventHandler
和EventHandle <T>
,以便定義事件。上面的例子中,刪去了自己定義的委托轉而使用EventHandle<T>
泛型委托。
public event EventHandler<MessageArrivedEventArgs> MessageArrived;
上面提到的兩個委托類型EventHandler
和EventHandle <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#入門經典(第六版)》