事件
事件含義
事件由對(duì)象引發(fā),通過(guò)我們提供的代碼來(lái)處理别瞭。一個(gè)事件我們必須訂閱(Subscribe)他們,訂閱一個(gè)事件的含義就是提供代碼,在這個(gè)事件發(fā)生時(shí)執(zhí)行這些代碼,這些代碼稱為事件處理程序必盖。
一個(gè)事件可以被多個(gè)事件處理程序訂閱失驶,在這個(gè)事件發(fā)生時(shí),這些處理程序都會(huì)被執(zhí)行眷蜓。事件處理程序可以在該事件的對(duì)象所處的類中福荸,也可以在其他類中。
事件處理程序本身就是一個(gè)普通的方法买窟,對(duì)這個(gè)方法的唯一限制是:必須匹配事件所要求的返回類型和參數(shù),這個(gè)限制是事件定義的一部分盏浇,由一個(gè)委托指定班挖。
處理事件
要處理事件,需要提供一個(gè)事件處理方法來(lái)訂閱事件迅办,方法的返回類型和參數(shù)必須匹配事件指定的委托蓬豁。下面使用一個(gè)簡(jiǎn)單的計(jì)時(shí)器對(duì)象來(lái)引發(fā)事件,調(diào)用一個(gè)處理方法地粪。
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);
}
}
}
這個(gè)示例運(yùn)行后將會(huì)打印字符取募,一個(gè)一個(gè)打印。那么我來(lái)解釋一下上面的栗子蟆技。
Timer 是一個(gè)定時(shí)器付魔,每隔一段時(shí)間會(huì)觸發(fā)一個(gè)事件,這個(gè)時(shí)間在構(gòu)造函數(shù)里給出几苍,這里是100ms妻坝。要引發(fā)事件伸眶,首先要把這個(gè)定時(shí)器跑起來(lái)厘贼,就是
myTimer.Start();
這個(gè)Timer呢有一個(gè)事件叫做Elapsed
,我們就要用一個(gè)方法去訂閱它圣拄。條件就是必須匹配System.Timers.ElapsedEventHandler
這個(gè)委托類型的返回類型和參數(shù)。這個(gè)委托來(lái)自.Net Framework
標(biāo)準(zhǔn)委托岳掐。它指定了如下的返回類型和參數(shù)饭耳。
void <MethodName> (Object source, ElapsedEventArgs e);
其中的source
參數(shù)是Timer對(duì)象本身的引用,ElapsedEventArgs
是對(duì)象的一個(gè)實(shí)例寞肖。后面繼續(xù)介紹衰腌。
在上面給出的代碼中有滿足這個(gè)委托返回類型和參數(shù)列表的方法static void WriteChar(object source, ElapsedEventArgs e) {
Console.Write(displayString[counter++ % displayString.Length]);
}
先不管后面那個(gè)WriteChar2觅赊。 里面的方法在屏幕上一次輸出字符串中的字符。 好了,事件有了规脸,對(duì)應(yīng)的方法有了熊咽,剩下的工作就是去訂閱這個(gè)事件。 使用`+=`運(yùn)算符被因,給事件添加一個(gè)處理程序衫仑,形式是使用事件處理方法初始化一個(gè)新的**委托實(shí)例**。但是方法不止一種: 1. `myTimer.Elapsed += new ElapsedEventHandler(WriteChar);` 2. `Timer.Elapsed += WriteChar;` 上面兩個(gè)方式都是可以的粥鞋,使用第2種編譯器會(huì)根據(jù)使用的上下文來(lái)指定委托類型呻粹。壞處就是降低可讀性苏研,你不能一下就知道這個(gè)委托類型是什么。 其實(shí)還有方法: **Lambda表達(dá)式**
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)`也是可以省略的筹燕。 至此衅鹿,所有的工作都完成啦。 再理一遍:定時(shí)器每隔100ms引發(fā)一個(gè)事件糠涛,這個(gè)事件被我們的方法訂閱兼犯,事件觸發(fā)后去找那個(gè)訂閱的處理程序,執(zhí)行那個(gè)方法砸脊,在屏幕上輸出內(nèi)容凌埂。
創(chuàng)建事件
上面使用了Timer自帶的事件,我們也可以創(chuàng)建我們自己的事件埃疫。這里是一個(gè)即時(shí)消息傳送程序孩哑,創(chuàng)建一個(gè)Connection
對(duì)象,這個(gè)對(duì)象引發(fā)由Display
對(duì)象處理的事件胳蛮。
首先定義一個(gè)委托類型丛晌,你一定知道用它干嘛澎蛛。
public delegate void MessageHandler(string messageText);
這個(gè)委托類型稱為MessageHandler,有一個(gè)
void
的方法簽名督勺,有一個(gè)string
參數(shù)斤贰。
在Connection
這個(gè)類里定義一個(gè)事件
public event MessageHandler MessageArrived
給事件命名,這里使用
MessageArrived
瓷叫,在聲明時(shí)送巡,使用event
關(guān)鍵字骗爆,并指定要使用的委托類型MessageHandler
。這樣聲明以后煮寡,就可以引發(fā)它。方法是按名稱來(lái)調(diào)用它薇组。例如:
MessageArrived("This is a message.");
為什么是這樣呢坐儿?
MessageArrived
這是一個(gè)委托實(shí)例,按照委托的方法傳參炭菌。
要是定義的委托不需要參數(shù)站叼,那就這樣MessageArrived();
要是參數(shù)多就要用更多的代碼區(qū)實(shí)現(xiàn)尽楔。
先上代碼吧第练。
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))
這里得到一個(gè)隨機(jī)數(shù)娇掏,得到0時(shí)引發(fā)事件,后面的MessageArrived!=null
檢查事件是否有被訂閱下梢,如果沒(méi)有被訂閱那么MessageArrived=null
不會(huì)引發(fā)事件塞蹭。
下面是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)
滿足上面定義的委托返回類型和參數(shù)番电,可以用它來(lái)訂閱事件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();
}
}
}
下面是運(yùn)行結(jié)果
多用途的事件處理程序
前面使用的Timer.Elapsed
事件的委托包含了事件處理程序中常見(jiàn)的兩類參數(shù)
-
object source
引發(fā)事件的對(duì)象的引用 -
ElapsedEventArgs e
由事件傳送的參數(shù)
在事件中使用object
類型的原因是漱办,我們常常要為由不同對(duì)象引發(fā)的幾個(gè)相同事件使用同一個(gè)事件處理程序娩井,但仍要指定是哪個(gè)對(duì)象生成了事件。
下面擴(kuò)展上面的示例洞辣。
添加一個(gè)新類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();
}
發(fā)送一個(gè)引發(fā)事件的對(duì)象引用所宰,把這個(gè)對(duì)象作為事件處理程序的一個(gè)參數(shù),就可以為不同的對(duì)象定制處理程序的響應(yīng)婴谱∏可以利用這個(gè)對(duì)象訪問(wèn)源對(duì)象麦向,包括它的屬性。
通過(guò)發(fā)送包含在派生于System.EventArgs
類中的參數(shù)话告,就可以將其他信息作為參數(shù)提供卵慰。這些參數(shù)也得益于多態(tài)性。為MessageArrived
事件定義一個(gè)處理程序:
public void DisplayMessage(object source, MessageArrivedEventArgs e)
{
Console.WriteLine("Message arrived from: {0}",
((Connection)source).Name);
Console.WriteLine("Message Text: {0}", e.Message);
}
這個(gè)處理程序可以處理不限于Timers.Elapsed
的事件病线。但是要修改里面的代碼鲤嫡,并注意檢查null
值暖眼。
EventHandler
和泛型EventHandler<T>
類型
這里有一個(gè)規(guī)范,事件處理程序的返回類型應(yīng)為void
,參數(shù)應(yīng)該是兩個(gè)赡突,第一個(gè)是object
区赵,第二個(gè)參數(shù)是派生于System.EventArgs
。為此.Net
提供了兩個(gè)委托類型EventHandler
和EventHandle <T>
漱受,以便定義事件。上面的例子中絮记,刪去了自己定義的委托轉(zhuǎn)而使用EventHandle<T>
泛型委托虐先。
public event EventHandler<MessageArrivedEventArgs> MessageArrived;
上面提到的兩個(gè)委托類型EventHandler
和EventHandle <T>
蛹批,他們的返回類型都是void
,參數(shù)列表
public delegate void EventHandler(object sender, System.EventArgs e)
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)
這顯然簡(jiǎn)化了我們的代碼。一般腐芍,在定義事件時(shí)士飒,最好使用這些委托類型蒸播。如果事件不需要事件實(shí)參數(shù)據(jù)茎活,仍然可以用EventHandler
委托類型项玛,但是要傳遞EventArgs.Empty
作為實(shí)參弱判。
也可以為事件提供返回類型,但這有一個(gè)問(wèn)題开伏。引發(fā)給定的事件遭商,可能會(huì)調(diào)用多個(gè)事件處理程序。如果這些處理程序都返回一個(gè)值巫玻,那么我們只能得到最后一個(gè)訂閱該事件的處理程序的返回值祠汇。有些情況下可能是有用的可很,但最好使用void
作為返回類型。
匿名方法
前面提到過(guò)匿名方法苇本,是為用作委托目的而創(chuàng)建的。要?jiǎng)?chuàng)建匿名方法笛厦,使用以下代碼:
delegate(parameters){
//具體代碼
}
其中
parameters
是一個(gè)參數(shù)列表康栈,這些參數(shù)列表匹配正在實(shí)例化的委托類型啥么,由匿名方法使用,例如:
delegate(Connection source, MessageArrivedEventArgs e){
> Console.WriteLine("Message arrived from: {0}",
((Connection)source).Name);
Console.WriteLine("Message Text: {0}", e.Message);
> };
使用下面的代碼可以完全繞過(guò)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);
}
完
示例代碼和部分內(nèi)容來(lái)自《C#入門經(jīng)典(第六版)》