Share via


在 Windows 執行階段元件中自訂事件和事件存取子

Windows 執行階段的 .NET Framework 支援可隱藏 Windows 執行階段事件模式和 .NET Framework 事件模式之間的差異,以便輕易在 Windows 執行階段元件中宣告事件。但是,當您在 Windows 執行階段元件中宣告自訂事件存取子時,必須遵循 Windows 執行階段模式。

當您註冊以在 Windows 執行階段中處理事件時,add 存取子會傳回語彙基元。若要移除註冊,您可將這個語彙基元傳遞至 remove 存取子。這表示 Windows 執行階段事件的 add 和 remove 存取子與您所用的存取子具有不同的簽章。

幸運的是,Visual Basic 和 C# 編譯器會簡化這個處理序:當您在 Windows 執行階段元件中宣告具有自訂存取子的事件時,編譯器會自動使用 Windows 執行階段模式。例如,若 add 存取子未傳回語彙基元,就會發生編譯器錯誤。.NET Framework 提供兩個類型來支援實作:

  • EventRegistrationToken 結構代表語彙基元。

  • EventRegistrationTokenTable<T> 類別會建立語彙基元,並維護語彙基元和事件處理常式之間的對應。泛型型別引數是事件引數型別。您為每個事件建立此類別的執行個體時,第一次為該事件註冊事件處理常式。

NumberChanged 事件的下列程式碼顯示 Windows 執行階段事件的基本模式。在這個範例中,事件引數物件的建構函式 (NumberChangedEventArgs) 會採用表示已變更數值的單一整數參數。

注意事項注意事項

這就是編譯器用於您在 Windows 執行階段元件中宣告之一般事件的相同模式。

    private EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>> 
        m_NumberChangedTokenTable = null;

    public event EventHandler<NumberChangedEventArgs> NumberChanged
    {
        add
        {
            return EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
                .GetOrCreateEventRegistrationTokenTable(ref m_NumberChangedTokenTable)
                .AddEventHandler(value);
        }
        remove
        {
            EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
                .GetOrCreateEventRegistrationTokenTable(ref m_NumberChangedTokenTable)
                .RemoveEventHandler(value);
        }
    }

    internal void OnNumberChanged(int newValue)
    {
        EventHandler<NumberChangedEventArgs> temp = 
            EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
            .GetOrCreateEventRegistrationTokenTable(ref m_NumberChangedTokenTable)
            .InvocationList;
        if (temp != null)
        {
            temp(this, new NumberChangedEventArgs(newValue));
        }
    }
Private m_NumberChangedTokenTable As  _
    EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs))

Public Custom Event NumberChanged As EventHandler(Of NumberChangedEventArgs)

    AddHandler(ByVal handler As EventHandler(Of NumberChangedEventArgs))
        Return EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs)).
            GetOrCreateEventRegistrationTokenTable(m_NumberChangedTokenTable).
            AddEventHandler(handler)
    End AddHandler

    RemoveHandler(ByVal token As EventRegistrationToken)
        EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs)).
            GetOrCreateEventRegistrationTokenTable(m_NumberChangedTokenTable).
            RemoveEventHandler(token)
    End RemoveHandler

    RaiseEvent(ByVal sender As Class1, ByVal args As NumberChangedEventArgs)
        Dim temp As EventHandler(Of NumberChangedEventArgs) = _
            EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs)).
            GetOrCreateEventRegistrationTokenTable(m_NumberChangedTokenTable).
            InvocationList
        If temp IsNot Nothing Then
            temp(sender, args)
        End If
    End RaiseEvent
End Event

static (在 Visual Basic 中為 Shared) GetOrCreateEventRegistrationTokenTable 方法會延遲建立事件的 EventRegistrationTokenTable<T> 物件執行個體。將保存語彙基元資料表執行個體的類別層級欄位傳遞給此方法。如果此欄位空白,這個方法會建立資料表、在欄位中儲存資料表的參考,然後傳回該資料表的參考。如果欄位已經包含語彙基元資料表參考,這個方法只會傳回該參考。

重要

為了確保執行緒安全,保存事件之 EventRegistrationTokenTable<T> 執行個體的欄位必須是類別層級的欄位。如果是類別層級的欄位,則 GetOrCreateEventRegistrationTokenTable 方法會確保當多個執行緒嘗試建立語彙基元資料表時,所有執行緒都會取得此資料表的相同執行個體。對於指定的事件,GetOrCreateEventRegistrationTokenTable 方法的所有呼叫都必須使用相同類別層級的欄位。

在 remove 存取子和 RaiseEvent 方法中 (在 C# 中為 OnRaiseEvent 方法) 呼叫 GetOrCreateEventRegistrationTokenTable 方法,可確保在加入任何事件處理常式委派之前呼叫這些方法,不會發生例外狀況。

在 Windows 執行階段事件模式中使用的其他 EventRegistrationTokenTable<T> 類別成員包括下列各項:

  • AddEventHandler 方法會產生事件處理常式委派的語彙基元、在資料表中儲存委派、將它加入至叫用清單,然後傳回語彙基元。

  • RemoveEventHandler(EventRegistrationToken) 方法多載會從資料表和叫用清單中移除此委派。

    注意事項注意事項

    AddEventHandlerRemoveEventHandler(EventRegistrationToken) 方法會鎖定資料表,協助確保執行緒安全。

  • InvocationList 屬性傳回的委派包含目前已註冊要處理事件的所有事件處理常式。使用此委派來引發事件,或使用 Delegate 類別的方法個別叫用處理常式。

    注意事項注意事項

    建議您遵循本文前述範例中顯示的模式,先將委派複製到暫存變數,再予以叫用。這可避免某一個執行緒移除最後一個處理常式的競爭情形,進而讓委派在另一個執行緒嘗試叫用委派之前減少至 null。委派是不可變的,因此複本仍然有效。

視情況將自己的程式碼放在存取子中。如果執行緒安全有問題,您必須為程式碼提供自己的鎖定。

C# 使用者:當您在 Windows 執行階段事件模式中寫入自訂事件存取子時,編譯器不會提供一般語法快速鍵。如果您在程式碼中使用事件名稱,則會產生錯誤。

Visual Basic 使用者:在 .NET Framework 中,事件只是表示所有已註冊事件處理常式的多點傳送委派。引發事件只是表示叫用委派。Visual Basic 語法通常會隱藏與委派的互動,而且編譯器會在叫用委派之前先行複製 (如執行緒安全相關注意事項所述)。當您在 Windows 執行階段元件中建立自訂事件時,您必須直接處理委派。這也表示您可以使用 MulticastDelegate.GetInvocationList 方法取得一個陣列,如果您想要個別叫用處理常式,該陣列會包含每個事件處理常式的個別委派。

請參閱

工作

HOW TO:實作自訂事件存取子 (C# 程式設計手冊)

參考

事件 (C# 程式設計手冊)

其他資源

事件 (Visual Basic)

適用於 Windows 市集應用程式和 Windows 執行階段的 .NET Framework 支援