C#事件

事件

事件含義

事件由對(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é)果

運(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è)委托類型EventHandlerEventHandle <T>漱受,以便定義事件。上面的例子中絮记,刪去了自己定義的委托轉(zhuǎn)而使用EventHandle<T>泛型委托虐先。

public event EventHandler<MessageArrivedEventArgs> MessageArrived;

上面提到的兩個(gè)委托類型EventHandlerEventHandle <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)典(第六版)》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市氯迂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌禁灼,老刑警劉巖轿曙,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件导帝,死亡現(xiàn)場(chǎng)離奇詭異您单,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)平酿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門悦陋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)叨恨,“玉大人,你說(shuō)我怎么就攤上這事×《荆” “怎么了蚕甥?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵菇怀,是天一觀的道長(zhǎng)爱沟。 經(jīng)常有香客問(wèn)我,道長(zhǎng)呼伸,這世上最難降的妖魔是什么括享? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任铃辖,我火速辦了婚禮,結(jié)果婚禮上仁卷,老公的妹妹穿的比我還像新娘成洗。我一直安慰自己藏否,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著淆储,像睡著了一般本砰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舔株,一...
    開(kāi)封第一講書(shū)人閱讀 51,718評(píng)論 1 305
  • 那天载慈,我揣著相機(jī)與錄音,去河邊找鬼办铡。 笑死寡具,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的嫂伞。 我是一名探鬼主播拯钻,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼粪般,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了匙监?” 一聲冷哼從身側(cè)響起小作,我...
    開(kāi)封第一講書(shū)人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤顾稀,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后静秆,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體抚笔,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡殊橙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了叠纹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吊洼。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖递沪,靈堂內(nèi)的尸體忽然破棺而出综液,到底是詐尸還是另有隱情,我是刑警寧澤檩奠,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布埠戳,位于F島的核電站蕉扮,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏屁使。R本人自食惡果不足惜奔则,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一易茬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧除嘹,春花似錦岸蜗、人聲如沸璃岳。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)犁柜。三九已至馋缅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間萤悴,已是汗流浹背覆履。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工硝全, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人伟众。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓赂鲤,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親找爱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子泡孩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理吮播,服務(wù)發(fā)現(xiàn)眼俊,斷路器,智...
    卡卡羅2017閱讀 134,667評(píng)論 18 139
  • 事件 基本用法 關(guān)鍵字event环戈,聲明格式為: public event <委托類型> <事件對(duì)象> 事件的處理方...
    CieloSun閱讀 2,021評(píng)論 0 0
  • 一院塞、理解事件事件采用發(fā)布/訂閱模型,其中發(fā)行者決定在什么情況下引發(fā)事件拦止,而訂戶決定為響應(yīng)事件而執(zhí)行的操作县遣。事件可以...
    CarlDonitz閱讀 298評(píng)論 0 0
  • Asio分為獨(dú)立版和Boost版。兩者使用方法基本一致汹族,只是頭文件不同艺玲。Boost版是作為Boost的子庫(kù)提供的。...
    果凍蝦仁閱讀 4,312評(píng)論 0 0
  • 《你所擔(dān)心的事情九成都不會(huì)發(fā)生》這是一本書(shū)名鞠抑,也是生活中的一些事實(shí)饭聚。你們有沒(méi)有經(jīng)常瞎操心了,瞎操心只會(huì)讓我們?cè)?..
    誠(chéng)思心夢(mèng)閱讀 163評(píng)論 0 3