C#圖解教程 第十四章 事件

事件

發佈者和訂閱者


不少程序都有一個共同的需求,既當一個特定的程序事件發生時,程序的其餘部分能夠獲得該事件已經發生的通知。 
發佈者/訂閱者模式(publisher/subscriber pattern)能夠知足這種需求。html


  • 發佈者(publisher) 發佈某個事件的類或結構,其餘類能夠在該事件發生時獲得通知
  • 訂閱者(subscriber) 註冊並在事件發生時獲得通知的類或結構
  • 事件處理程序(event handler) 由訂閱者註冊到事件的方法,在發佈者觸發事件時執行。事件處理程序可定義在事件所在的類或結構中,也可定義在不一樣的類或結構中
  • 觸發(raise)事件 調用(invoke)或觸發(fire)事件的術語。當事件觸發時,全部註冊到它的方法都會被依次調用

前一章介紹了委託。事件的不少部分都與委託相似。實際上,事件就像專門用於特殊用途的簡單委託。事件包含了一個私有的委託。 
 
有關事件的私有委託須要瞭解的重要事項以下:編程

  • 事件提供了對它的私有控制委託的結構化訪問。即,你沒法直接訪問委託
  • 事件中可用的操做比委託少,對於事件咱們只可添加、刪除或調用事件處理程序
  • 事件被觸發時,它調用委託來依次調用調用列表中的方法

源代碼組件概覽


須要在事件中使用的代碼有5部分。併發

  • 委託類型聲明 事件和事件處理程序必須有共同的簽名和返回類型,它們經過委託類型進行描述
  • 事件處理程序聲明 訂閱者類中會在事件觸發時執行的方法聲明。它們不必定是有顯式命名的方法,還能夠是第13章描述的匿名方法或Lambda表達式
  • 事件聲明 發佈者類必須聲明一個訂閱者類能夠註冊的事件成員。當聲明的事件爲public時,稱爲發佈了事件
  • 事件註冊 訂閱者必須訂閱事件才能在它被觸發時獲得通知
  • 觸發事件的代碼 發佈者類中「觸發」事件並致使調用註冊的全部事件處理程序的代碼

 

聲明事件


發佈者類必須提供事件對象。建立事件比較簡單–只須要委託類型和名字。事件聲明的語法以下代碼所示。 
代碼中聲明瞭CountADozen事件。框架

  • 事件聲明在一個類中
  • 它須要委託類型的名稱,任何附加到事件(如註冊)的處理程序都必須與委託類型的簽名和返回類型匹配
  • 它聲明爲public,這樣其餘類和結構能夠在它上面註冊事件處理程序
  • 不能使用對象建立表達式(new 表達式)來建立對象
class Incrementer
{
           關鍵字   委託類型      事件名
             ↓        ↓           ↓
    public event EventHandler CountedADozen;
    ...
}

能夠經過使用逗號分隔同時聲明多個事件。異步

public event EventHandler MyEvent1,MyEvent2,OtherEvent;

還可使用static關鍵字讓事件變成靜態函數

public static event EventHandler CountedADozen;

事件是成員 
一個常見誤解是把事件認爲是類型。和方法、屬性同樣,事件是類或結構的成員,這一點引出幾個重要特性。this

  • 因爲事件是成員:
    • 咱們不能在一段可執行代碼中聲明事件
    • 它必須聲明在類或結構中
  • 事件成員被隱式自動初始化爲null

事件聲明須要委託類型的名字,咱們能夠聲明一個委託類型或使用已存在的。若是咱們聲明一個委託類型,它必須指定事件保存的方法的簽名和返回類型。 
BCL(Base Class Library,基類庫)聲明瞭一個叫作EventHandler的委託,專門用於系統事件。設計

訂閱事件


  • 使用+=運算符來爲事件增長事件處理程序
  • 事件處理程序的規範能夠是如下任意一種
    • 實例方法的名稱
    • 靜態方法的名稱
    • 匿名方法
    • Lambda表達式

例:爲CountedADozen事件增長3個方法。3d

incrementer.CountedADozen+=IncrementDozensCount;//實例方法
incrementer.CountedADozen+=ClassB.CounterHandlerB;//靜態方法
mc.CountedADozen+=new EventHandler(cc.CounterHandlerC);//委託形式

例:Lambda表達式和匿名方法code

incrementer.CountedADozen+=()=>DozensCount++;//Lambda表達式
incrementer.CountedADozen+=delegate{DozensCount++;};//匿名方法

觸發事件


事件成員自己只保存了須要被調用的事件處理程序。若是事件沒觸發,什麼都不會發生。咱們須要確保在合適的時候有代碼來作這件事情。 
例:下面代碼觸發了CountedADozen事件。

  • 在觸發事件前和null比較,從而查看是否包含事件處理程序,若是事件是null,則表示沒有,不能執行
  • 觸發事件的語法和調用方法同樣
    • 使用事件名稱,後面跟參數列表
    • 參數列表需與事件委託類型相匹配
if(CountedADozen!=null)
{
    CountedADozen(source,args);
}

把事件聲明和觸發事件的代碼放在一塊兒便有了以下的發佈者類聲明。 
下面展現了整個程序,代碼須要注意的地方以下:

  • 在構造函數中,Dozens類訂閱事件,將IncrementDozensCount做爲事件處理程序
  • 在Incrementer類的DoCount方法中,每增加12個數就觸發CountedADozen事件
delegate void Handler();    //聲明委託
//發佈者
class Incrementer
{
    public event Handler CountedADozen;//建立事件併發布
    void DoCount(object source,EventArgs args)
    {
        for(int i=1;i<100;i++)
        {
            if((i%12==0)&&(CountedADozen!=null))
            {
                CountedADozen(source,args);
            }
        }
    }
}
//訂閱者
class Dozens
{
    public int DozensCount{get;private set;}
    public Dozens(Incrementer incrementer)
    {
        DozensCount=0;
        incrementer.CountedADozen+=IncrementDozensCount;//訂閱事件
    }
    void IncrementDozensCount()//聲明事件處理程序
    {
        DozensCount++;
    }
}
class Program
{
    static void Main()
    {
        var incrementer=new Incrementer();
        var dozensCounter=new Dozens(incrementer);
        incrementer.DoCount();
        Console.WriteLine("Number of dozens = {0}",dozensCounter.DozensCount);
    }
}

標準事件的用法


GUI編程是事件驅動的,即程序運行時,它能夠在任什麼時候候被事件打斷,好比鼠標點擊,按下按鍵或系統定時器。在這些狀況發生時,程序須要處理事件而後繼續其餘事情。 
程序事件的異步處理是使用C#事件的絕佳場景。Windows GUI編程普遍的使用事件,對於事件的使用,.NET框架提供了一個標準模式。事件使用的標準模式根本就是System命名空間聲明的EventHandler委託類型。EventHandler委託類型的聲明代碼以下。

  • 第一個參數用來保存觸發事件的對象的引用。因爲是object類型,因此能夠匹配任何類型的實例
  • 第二個參數用來保存狀態信息,指明什麼類型適用於該程序
  • 返回類型是void
public delegate void EventHandler(object sender,EventArgs e);

EventHandler委託類型的第二個參數是EventArgs類的UI項,它聲明在System命名空間中。既然第二個參數用於傳遞數據,你可能會誤認爲EventArgs類的對象應該能夠保存一些類型的數據。

  • EventArgs設計爲不能傳遞任何數據。它用於不須要傳遞數據的事件處理程序–一般會被忽略
  • 若是你但願傳遞數據,必須聲明一個派生自EventArgs的類,使用合適的字段來保存須要傳遞的數據

例:Incrementer+EventHandler

  • 在聲明中使用系統定義的EventHandler委託替換Handler
  • 訂閱者中聲明的事件處理程序簽名必須與事件委託(object、EventArgs參數)的簽名(和返回類型)匹配。對於IncrementDozensCount事件處理程序來講,該方法忽略了正式參數
  • 觸發事件的代碼在調用事件時必須使用適當的參數類型的對象
public delegate void EventHandler(object sender,EventArgs e);
//發佈者
class Incrementer
{
    public event EventHandler CountedADozen;//建立事件併發布
    public void DoCount()
    {
        for(int i=1;i<100;i++)
        {
            if((i%12==0)&&(CountedADozen!=null))
            {
                CountedADozen(this,null);
            }
        }
    }
}
//訂閱者
class Dozens
{
    public int DozensCount{get;private set;}
    public Dozens(Incrementer incrementer)
    {
        DozensCount=0;
        incrementer.CountedADozen+=IncrementDozensCount;//訂閱事件
    }
    void IncrementDozensCount(object source,EventArgs e)//聲明事件處理程序
    {
        DozensCount++;
    }
}
class Program
{
    static void Main()
    {
        var incrementer=new Incrementer();
        var dozensCounter=new Dozens(incrementer);
        incrementer.DoCount();
        Console.WriteLine("Number of dozens = {0}",dozensCounter.DozensCount);
    }
}
經過擴展EventArgs來傳遞數據

爲了向EventArgs傳入數據,而且符合標準慣例,咱們須要聲明一個派生自EventArgs的自定義類,用於保存須要傳入的數據。類的名稱應該以EventArgs結尾。 
例:聲明自定義的EventArgs類,它將字符串存儲在IterationCount字段中。

public class IncrementerEventArgs:EventArgs
{
    public int IterationCount{get;set;}
}

除了自定義類外,你還須要一個使用自定義類的委託類型。要獲取該類,可使用泛型(第17章)版本的委託EventHandler<>。要使用泛型委託,須要作到如下兩點:

  • 將自定義類的名稱放在尖括號內
  • 在須要使用自定義委託類型的時候使用整個字符串。

例:使用了自定義類和自定義委託的事件示例

public class IncrementerEventArgs:EventArgs
{
    public int IterationCount{get;set;}
}
//發佈者
class Incrementer
{
                       使用自定義類的泛型委託
                                ↓
    public event EventHandler<IncrementerEventArgs> CountedADozen;//建立事件併發布
    public void DoCount()
    {
        IncrementerEventArgs args=new IncrementerEventArgs();
        for(int i=1;i<100;i++)
        {
            if((i%12==0)&&(CountedADozen!=null))
            {
                args.IterationCount=i;
                CountedADozen(this,args);
            }
        }
    }
}
//訂閱者
class Dozens
{
    public int DozensCount{get;private set;}
    public Dozens(Incrementer incrementer)
    {
        DozensCount=0;
        incrementer.CountedADozen+=IncrementDozensCount;//訂閱事件
    }
    void IncrementDozensCount(object source,IncrementerEventArgs e)//聲明事件處理程序
    {
        Console.WriteLine("Incremented at iteration: {0} in {1}",e.IterationCount,source.ToString());
        DozensCount++;
    }
}
class Program
{
    static void Main()
    {
        var incrementer=new Incrementer();
        var dozensCounter=new Dozens(incrementer);
        incrementer.DoCount();
        Console.WriteLine("Number of dozens = {0}",dozensCounter.DozensCount);
    }
}

移除事件處理程序

用完事件處理程序後,可使用-=運算符把事件處理程序從事件中移除。

例:移除事件處理程序示例

class Publisher
{
    public event EventHandler SimpleEvent;
    public void RaiseTheEvent(){SimpleEvent(this,null);}
}
class Subscriber
{
    public void MethodA(object o,EventArgs e)
    {
        Console.WriteLine("AAA");
    }
    public void MethodB(object o,EventArgs e)
    {
        Console.WriteLine("BBB");
    }
}
class Program
{
    static void Main()
    {
        var p=new Publisher();
        var s=new Subscriber();
        p.SimpleEvent+=s.MethodA;
        p.SimpleEvent+=s.MethodB;
        p.RaiseTheEvent();
        Console.WriteLine("\r\nRemove MethodB");
        p.SimpleEvent-=s.MethodB;
        p.RaiseTheEvent();
    }
}

若是一個處理程序向事件註冊了屢次,那麼移除程序時,將只移除列表中該處理程序的最後一個實例。

事件訪問器


以前我提到+=和-=運算符是事件容許的惟一運算符。看到這裏咱們應該知道,這些運算符有預約義行爲。 
咱們能夠修改這些運算符的行爲,而且使用它們時可讓事件執行任何咱們但願的自定義代碼。但這是高級主題,此處只作簡單介紹。 
要改變這兩個運算符的操做,能夠爲事件定義事件訪問器。

  • 有兩個訪問器:add和remove
  • 聲明事件的訪問器看上去和聲明一個屬性差很少

例:具備訪問器的事件聲明。兩個訪問器都有隱式值參數value,它接受實例或靜態方法的引用。

public event EventHandler CountedADozen
{
    add
    {
        ...    //執行+=運算符的代碼
    }
    remove
    {
        ...    //執行-=運算符的代碼
    }
}

聲明事件訪問器後,事件不包含任何內嵌委託對象。咱們必須實現本身的機制來存儲和移除事件註冊方法。 
事件訪問器表現爲void方法,即沒有返回值。

from: http://www.cnblogs.com/moonache/p/6340222.html

相關文章
相關標籤/搜索