Custom events and event accessors in Windows Runtime Components

 

.NET Framework support for the Windows Runtime makes it easy to declare events in Windows Runtime Components by hiding the differences between the Windows Runtime event pattern and the .NET Framework event pattern. However, when you declare custom event accessors in a Windows Runtime component, you must follow the Windows Runtime pattern.

When you register to handle an event in the Windows Runtime, the add accessor returns a token. To unregister, you pass this token to the remove accessor. This means that the add and remove accessors for Windows Runtime events have different signatures from the accessors you're used to.

Fortunately, the Visual Basic and C# compilers simplify this process: When you declare an event with custom accessors in a Windows Runtime component, the compilers automatically use the Windows Runtime pattern. For example, you get a compiler error if your add accessor doesn't return a token. The .NET Framework provides two types to support the implementation:

  • The EventRegistrationToken structure represents the token.

  • The EventRegistrationTokenTable<T><T><'T>(Of T) class creates tokens and maintains a mapping between tokens and event handlers. The generic type argument is the event argument type. You create an instance of this class for each event, the first time an event handler is registered for that event.

The following code for the NumberChanged event shows the basic pattern for Windows Runtime events. In this example, the constructor for the event argument object, NumberChangedEventArgs, takes a single integer parameter that represents the changed numeric value. 

Note

This is the same pattern the compilers use for ordinary events that you declare in a Windows Runtime component.

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

The static (Shared in Visual Basic) GetOrCreateEventRegistrationTokenTable method creates the event’s instance of the EventRegistrationTokenTable<T><T><'T>(Of T) object lazily. Pass the class-level field that will hold the token table instance to this method. If the field is empty, the method creates the table, stores a reference to the table in the field, and returns a reference to the table. If the field already contains a token table reference, the method just returns that reference.

Important

To ensure thread safety, the field that holds the event’s instance of EventRegistrationTokenTable<T><T><'T>(Of T) must be a class-level field. If it is a class-level field, the GetOrCreateEventRegistrationTokenTable method ensures that when multiple threads try to create the token table, all threads get the same instance of the table. For a given event, all calls to the GetOrCreateEventRegistrationTokenTable method must use the same class-level field.

Calling the GetOrCreateEventRegistrationTokenTable method in the remove accessor and in the RaiseEvent method (the OnRaiseEvent method in C#) ensures that no exceptions occur if these methods are called before any event handler delegates have been added.

The other members of the EventRegistrationTokenTable<T><T><'T>(Of T) class that are used in the Windows Runtime event pattern include the following:

Note

We recommend that you follow the pattern shown in the example provided earlier in this article, and copy the delegate to a temporary variable before invoking it. This avoids a race condition in which one thread removes the last handler, reducing the delegate to null just before another thread tries to invoke the delegate. Delegates are immutable, so the copy is still valid.

Place your own code in the accessors as appropriate. If thread safety is an issue, you must provide your own locking for your code.

C# users: When you write custom event accessors in the Windows Runtime event pattern, the compiler doesn't provide the usual syntactic shortcuts. It generates errors if you use the name of the event in your code.

Visual Basic users: In the .NET Framework, an event is just a multicast delegate that represents all the registered event handlers. Raising the event just means invoking the delegate. Visual Basic syntax generally hides the interactions with the delegate, and the compiler copies the delegate before invoking it, as described in the note about thread safety. When you create a custom event in a Windows Runtime component, you have to deal with the delegate directly. This also means that you can, for example, use the MulticastDelegate.::..GetInvocationList method to get an array that contains a separate delegate for each event handler, if you want to invoke the handlers separately.

See Also

Events (Visual Basic)
Events (C# Programming Guide)
How to: Implement Custom Event Accessors (C# Programming Guide)
.NET Framework Support for Windows Store Apps and Windows Runtime