處理和引發事件

.NET 中的事件是以委派模型為基礎。 委派模型遵循觀察者設計模式,它可讓訂閱者向提供者註冊,並且接收通知。 事件發送者會推播事件已發生的通知,而事件接收器會收到該通告並定義對它的回應。 本文將描述委派模型的主要元件、如何在應用程式中使用事件,以及如何在程式碼中實作事件。

事件

事件是由物件傳送的訊息,用以表示發生動作。 這個動作可能是因使用者互動所造成,例如按一下按鈕,也可能起因於其他程式邏輯,如變更屬性值。 引發事件的物件稱為「事件發送者」。 事件發送者並不清楚哪個物件或方法會接收 (處理) 它所引發的事件。 事件通常是事件發送者的成員,例如,Click 事件是 Button 類別的成員,而 PropertyChanged 事件是實作 INotifyPropertyChanged 介面之類別的成員。

若要定義事件,您可以在事件類別的特徵標記中使用 C# 的 event 或 Visual Basic 的 Event 關鍵字,並指定事件之委派的型別。 委派將在下一節中描述。

一般而言,若要引發事件,您會加入標記為 protectedvirtual (C#) 或 ProtectedOverridable (Visual Basic) 的方法。 為這個 OnEventName 方法命名,例如 OnDataReceived。 這個方法應該接受一個指定事件資料物件 (EventArgs 型別或衍生型別之物件) 的參數。 您可以提供這個方法讓衍生類別覆寫引發事件的邏輯。 衍生類別一定要呼叫基底類別的 OnEventName 方法,以確保註冊的委派收到事件。

下列範例將示範如何宣告名為 ThresholdReached 的事件。 事件與 EventHandler 委派有關,而且會在名為 OnThresholdReached 的方法中引發。

class Counter
{
    public event EventHandler ThresholdReached;

    protected virtual void OnThresholdReached(EventArgs e)
    {
        ThresholdReached?.Invoke(this, e);
    }

    // provide remaining implementation for the class
}
Public Class Counter
    Public Event ThresholdReached As EventHandler

    Protected Overridable Sub OnThresholdReached(e As EventArgs)
        RaiseEvent ThresholdReached(Me, e)
    End Sub

    ' provide remaining implementation for the class
End Class

委派

委派是保存方法之參考的類型。 委派是透過特徵標記來宣告,該特徵標記會顯示所參考方法的傳回型別和參數,而且委派只能持有符合其特徵標記之方法的參考。 委派因此相當於 type-safe 函式指標或回呼。 委派宣告不足以定義委派類別。

委派在 .NET 中有多種用途。 在事件中,委派是事件來源和處理事件的程式碼之間的媒介 (或類似指標的機制)。 將委派類型加入事件宣告中就可以讓委派與事件產生關聯,如上一節的範例所示。 如需委派的詳細資訊,請參閱 Delegate 類別。

.NET 提供了 EventHandlerEventHandler<TEventArgs> 委派來支援大多數事件案例。 請針對所有未包含事件資料的事件使用 EventHandler 委派。 請針對有事件相關資料的事件使用 EventHandler<TEventArgs> 委派。 這些委派沒有傳回型別值,而且會採用兩個參數 (事件來源的物件和事件資料的物件)。

委派為多點傳送,這表示它們可以持有對一個以上事件處理方法的參考。 如需詳細資訊,請參閱 Delegate 參考頁面。 委派可讓事件處理更彈性並進行精細的控制。 委派會維護事件的已註冊事件處理常式清單,進而做為引發事件之類別的事件分派者。

對於無法使用 EventHandlerEventHandler<TEventArgs> 委派的情況,您可以定義委派。 需要定義委派的情況很少見,例如,當您必須使用無法辨識泛型的程式碼時。 您會在宣告中以 C# 的 delegate 與 Visual Basic 的 Delegate 關鍵字標記委派。 下列範例將示範如何宣告名為 ThresholdReachedEventHandler 的委派:

public delegate void ThresholdReachedEventHandler(object sender, ThresholdReachedEventArgs e);
Public Delegate Sub ThresholdReachedEventHandler(sender As Object, e As ThresholdReachedEventArgs)

事件資料

與事件相關聯的資料可以透過事件資料類別提供。 .NET 提供了許多事件資料類別,可讓您在應用程式中使用。 例如,SerialDataReceivedEventArgs 類別是 SerialPort.DataReceived 事件的事件資料類別。 .NET 遵循的命名模式是以 EventArgs 作為所有事件資料類型的結尾。 您可以藉由查看事件的委派,判斷與事件相關聯的事件資料類別。 例如,SerialDataReceivedEventHandler 委派會包含 SerialDataReceivedEventArgs 類別做為其中一個參數。

EventArgs 類別是所有事件資料類別的基底類型。 EventArgs 也是您在事件沒有任何相關資料時所用的類別。 如果您建立的事件主要是通知其他類別有事件發生,而不需要傳遞任何資料,則請在委派中加入 EventArgs 類別做為第二個參數。 您可以在未提供資料時傳遞 EventArgs.Empty 值。 EventHandler 委派會包含 EventArgs 類別做為參數。

當您要建立自訂事件資料類別時,請建立衍生自 EventArgs 的類別,然後提供傳遞事件相關資料所需的所有成員。 一般而言,您應該使用與 .NET 相同的命名模式,並以 EventArgs 作為事件資料類別名稱的結尾。

下列範例將示範名為 ThresholdReachedEventArgs 的事件資料類別。 其中包含所引發事件的特定屬性:

public class ThresholdReachedEventArgs : EventArgs
{
    public int Threshold { get; set; }
    public DateTime TimeReached { get; set; }
}
Public Class ThresholdReachedEventArgs
    Inherits EventArgs

    Public Property Threshold As Integer
    Public Property TimeReached As DateTime
End Class

事件處理常式

若要回應事件,請在事件接收器中定義事件處理常式方法。 這個方法必須符合您所處理事件的委派簽章。 在事件處理常式中,您會在事件引發時執行必要的動作,例如,在使用者按一下按鈕後收集使用者輸入。 若要在事件發生時收到通告,則事件處理常式方法必須訂閱事件。

下列範例將示範名為 c_ThresholdReached 的事件處理常式方法,該方法會比對 EventHandler 委派的簽章。 方法會訂閱 ThresholdReached 事件。

class ProgramTwo
{
    static void Main()
    {
        var c = new Counter();
        c.ThresholdReached += c_ThresholdReached;

        // provide remaining implementation for the class
    }

    static void c_ThresholdReached(object sender, EventArgs e)
    {
        Console.WriteLine("The threshold was reached.");
    }
}
Module Module1

    Sub Main()
        Dim c As New Counter()
        AddHandler c.ThresholdReached, AddressOf c_ThresholdReached

        ' provide remaining implementation for the class
    End Sub

    Sub c_ThresholdReached(sender As Object, e As EventArgs)
        Console.WriteLine("The threshold was reached.")
    End Sub
End Module

靜態和動態事件處理常式

.NET 可讓訂閱者以靜態或動態方式註冊事件通知。 靜態事件處理常式在其處理事件之類別的整個生命週期內都有效。 動態事件處理常式會在程式執行期間明確地啟動及停用,通常是為了回應某些條件式邏輯。 比方說,如果只有在某些情況下才需要事件通知,或如果應用程式提供多個事件處理常式且執行階段條件會定義一個要使用的適當處理常式,即可使用動態事件處理常式。 上一節中的範例示範了如何以動態方式加入事件處理常式。 如需詳細資訊,請參閱事件 (in Visual Basic) 及事件 (in C#)。

引發多個事件

如果您的類別會引發多個事件,編譯器將會為每個事件委派執行個體產生一個欄位。 如果事件數目很大,則可能無法接受每個委派一個欄位的儲存成本。 對於這些情況,.NET 提供了事件屬性,可以搭配您選擇的另一種資料結構來儲存事件委派。

事件屬性是由事件存取子伴隨的事件宣告所組成。 事件存取子是您定義用來將事件委派執行個體加入儲存區資料結構或從其中移除的方法。

注意

事件屬性會比事件欄位慢,因為每個事件委派都必須先擷取才能進行叫用。

記憶體和速度之間會有所折衷。 如果您的類別定義許多不常引發的事件,您會想要實作事件屬性。 如需詳細資訊,請參閱如何:使用事件屬性處理多個事件

標題 描述
如何:引發和使用事件 包含引發和使用事件的範例。
如何:使用事件屬性處理多個事件 示範如何使用事件屬性處理多個事件。
觀察者設計模式 描述設計模式,此模式可讓訂閱者向提供者註冊並從中接收通知。

另請參閱