Eventos y descriptores de acceso de eventos personalizados en componentes de Windows Runtime
La compatibilidad de .NET con componentes de Windows Runtime facilita la declaración de componentes de eventos al ocultar las diferencias entre el patrón de eventos de Plataforma universal de Windows (UWP) y el patrón de eventos de .NET. Sin embargo, al declarar descriptores de acceso de eventos personalizados en un componente de Windows Runtime, debes seguir el patrón usado en UWP.
Registro de eventos
Cuando se registra para controlar un evento en UWP, el descriptor de acceso add devuelve un token. Para anular el registro, pase este token al descriptor de acceso remove. Esto significa que los descriptores de acceso add y remove para eventos de UWP tienen firmas diferentes de los descriptores de acceso a los que se usa.
Afortunadamente, los compiladores de Visual Basic y C# simplifican este proceso: al declarar un evento con descriptores de acceso personalizados en un componente de Windows Runtime, los compiladores usan automáticamente el patrón de UWP. Por ejemplo, recibirá un error del compilador si el descriptor de acceso add no devuelve un token. .NET proporciona dos tipos para admitir la implementación:
- La estructura EventRegistrationToken representa el token.
- La clase T> EventRegistrationTokenTable<crea tokens y mantiene una asignación entre tokens y controladores de eventos. El argumento de tipo genérico es el tipo de argumento de evento. Se crea una instancia de esta clase para cada evento, la primera vez que se registra un controlador de eventos para ese evento.
El código siguiente para el evento NumberChanged muestra el patrón básico para eventos de UWP. En este ejemplo, el constructor del objeto de argumento de evento, NumberChangedEventArgs, toma un único parámetro entero que representa el valor numérico cambiado.
Nota Este es el mismo patrón que los compiladores usan para eventos normales que declaras en un componente de Windows Runtime.
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
El método getOrCreateEventRegistrationTokenTable estático (compartido en Visual Basic) crea la instancia del evento del objeto T> EventRegistrationTokenTable<de forma diferente. Pase el campo de nivel de clase que contendrá la instancia de la tabla de tokens a este método. Si el campo está vacío, el método crea la tabla, almacena una referencia a la tabla en el campo y devuelve una referencia a la tabla. Si el campo ya contiene una referencia de tabla de token, el método simplemente devuelve esa referencia.
Importante Para garantizar la seguridad de subprocesos, el campo que contiene la instancia del evento eventRegistrationTokenTable<T> debe ser un campo de nivel de clase. Si es un campo de nivel de clase, el método GetOrCreateEventRegistrationTokenTable garantiza que cuando varios subprocesos intenten crear la tabla de tokens, todos los subprocesos obtienen la misma instancia de la tabla. Para un evento determinado, todas las llamadas al método GetOrCreateEventRegistrationTokenTable deben usar el mismo campo de nivel de clase.
Llamar al método GetOrCreateEventRegistrationTokenTable en el descriptor de acceso remove y en el método RaiseEvent (el método OnRaiseEvent en C#) garantiza que no se produzca ninguna excepción si se llama a estos métodos antes de que se hayan agregado delegados del controlador de eventos.
Los demás miembros de la clase T> EventRegistrationTokenTable<que se usan en el patrón de eventos de UWP incluyen lo siguiente:
El método AddEventHandler genera un token para el delegado del controlador de eventos, almacena el delegado en la tabla, lo agrega a la lista de invocación y devuelve el token.
La sobrecarga del método RemoveEventHandler(EventRegistrationToken) quita el delegado de la tabla y de la lista de invocación.
Nota Los métodos AddEventHandler y RemoveEventHandler(EventRegistrationToken) bloquean la tabla para ayudar a garantizar la seguridad de los subprocesos.
La propiedad InvocationList devuelve un delegado que incluye todos los controladores de eventos que están registrados actualmente para controlar el evento. Use este delegado para generar el evento o use los métodos de la clase Delegate para invocar a los controladores individualmente.
Nota Se recomienda seguir el patrón que se muestra en el ejemplo proporcionado anteriormente en este artículo y copiar el delegado en una variable temporal antes de invocarlo. Esto evita una condición de carrera en la que un subproceso quita el último controlador, lo que reduce el delegado a NULL justo antes de que otro subproceso intente invocar el delegado. Los delegados son inmutables, por lo que la copia sigue siendo válida.
Coloque su propio código en los descriptores de acceso según corresponda. Si la seguridad de subprocesos es un problema, debe proporcionar su propio bloqueo para el código.
Usuarios de C#: al escribir descriptores de acceso de eventos personalizados en el patrón de eventos para UWP, el compilador no proporciona los métodos abreviados sintácticos habituales. Genera errores si usa el nombre del evento en el código.
Usuarios de Visual Basic: en .NET, un evento es solo un delegado de multidifusión que representa todos los controladores de eventos registrados. Generar el evento simplemente significa invocar al delegado. La sintaxis de Visual Basic generalmente oculta las interacciones con el delegado y el compilador copia el delegado antes de invocarlo, como se describe en la nota sobre la seguridad de subprocesos. Cuando creas un evento personalizado en un componente de Windows Runtime, tienes que tratar directamente con el delegado. Esto también significa que, por ejemplo, puede usar el método MulticastDelegate.GetInvocationList para obtener una matriz que contenga un delegado independiente para cada controlador de eventos, si desea invocar los controladores por separado.