C#事件

事件

事件含義

事件由對象引發,通過我們提供的代碼來處理。一個事件我們必須訂閱(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 提供了兩個委托類型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#入門經典(第六版)》

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,826評論 18 139
  • 事件 基本用法 關鍵字event,聲明格式為: public event <委托類型> <事件對象> 事件的處理方...
    CieloSun閱讀 2,038評論 0 0
  • 一、理解事件事件采用發布/訂閱模型,其中發行者決定在什么情況下引發事件,而訂戶決定為響應事件而執行的操作。事件可以...
    CarlDonitz閱讀 307評論 0 0
  • Asio分為獨立版和Boost版。兩者使用方法基本一致,只是頭文件不同。Boost版是作為Boost的子庫提供的。...
    果凍蝦仁閱讀 4,346評論 0 0
  • 《你所擔心的事情九成都不會發生》這是一本書名,也是生活中的一些事實。你們有沒有經常瞎操心了,瞎操心只會讓我們越...
    誠思心夢閱讀 166評論 0 3