在 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) 方法多載會從資料表和叫用清單中移除此委派。
注意事項 AddEventHandler 和 RemoveEventHandler(EventRegistrationToken) 方法會鎖定資料表,協助確保執行緒安全。
InvocationList 屬性傳回的委派包含目前已註冊要處理事件的所有事件處理常式。使用此委派來引發事件,或使用 Delegate 類別的方法個別叫用處理常式。
注意事項 建議您遵循本文前述範例中顯示的模式,先將委派複製到暫存變數,再予以叫用。這可避免某一個執行緒移除最後一個處理常式的競爭情形,進而讓委派在另一個執行緒嘗試叫用委派之前減少至 null。委派是不可變的,因此複本仍然有效。
視情況將自己的程式碼放在存取子中。如果執行緒安全有問題,您必須為程式碼提供自己的鎖定。
C# 使用者:當您在 Windows 執行階段事件模式中寫入自訂事件存取子時,編譯器不會提供一般語法快速鍵。如果您在程式碼中使用事件名稱,則會產生錯誤。
Visual Basic 使用者:在 .NET Framework 中,事件只是表示所有已註冊事件處理常式的多點傳送委派。引發事件只是表示叫用委派。Visual Basic 語法通常會隱藏與委派的互動,而且編譯器會在叫用委派之前先行複製 (如執行緒安全相關注意事項所述)。當您在 Windows 執行階段元件中建立自訂事件時,您必須直接處理委派。這也表示您可以使用 MulticastDelegate.GetInvocationList 方法取得一個陣列,如果您想要個別叫用處理常式,該陣列會包含每個事件處理常式的個別委派。