Eventi personalizzati e funzioni di accesso agli eventi nei componenti Windows Runtime

Il supporto di .NET per i componenti Windows Runtime semplifica la dichiarazione dei componenti eventi nascondendo le differenze tra il modello di eventi piattaforma UWP (Universal Windows Platform) (UWP) e il modello di eventi .NET. Tuttavia, quando dichiari funzioni di accesso agli eventi personalizzate in un componente Windows Runtime, devi seguire il modello usato nella piattaforma UWP.

Registrazione di eventi

Quando si esegue la registrazione per gestire un evento nella piattaforma UWP, la funzione di accesso aggiungi restituisce un token. Per annullare la registrazione, passare questo token alla funzione di accesso remove. Ciò significa che le funzioni di accesso add and remove per gli eventi UWP hanno firme diverse dalle funzioni di accesso usate.

Fortunatamente, i compilatori Visual Basic e C# semplificano questo processo: quando dichiari un evento con funzioni di accesso personalizzate in un componente Windows Runtime, i compilatori usano automaticamente il modello UWP. Ad esempio, viene visualizzato un errore del compilatore se la funzione di accesso add non restituisce un token. .NET offre due tipi per supportare l'implementazione:

  • La struttura EventRegistrationToken rappresenta il token.
  • La classe EventRegistrationTokenTable<T> crea token e gestisce un mapping tra token e gestori eventi. L'argomento di tipo generico è il tipo di argomento dell'evento. Si crea un'istanza di questa classe per ogni evento, la prima volta che un gestore eventi viene registrato per tale evento.

Il codice seguente per l'evento NumberChanged mostra il modello di base per gli eventi UWP. In questo esempio il costruttore per l'oggetto argomento evento NumberChangedEventArgs accetta un singolo parametro integer che rappresenta il valore numerico modificato.

Nota Questo è lo stesso modello usato dai compilatori per gli eventi ordinari dichiarati in un componente 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

Il metodo Static (Shared in Visual Basic) GetOrCreateEventRegistrationTokenTable crea l'istanza dell'evento dell'oggetto EventRegistrationTokenTable<T> in modo differito. Passare il campo a livello di classe che conterrà l'istanza della tabella token a questo metodo. Se il campo è vuoto, il metodo crea la tabella, archivia un riferimento alla tabella nel campo e restituisce un riferimento alla tabella. Se il campo contiene già un riferimento alla tabella dei token, il metodo restituisce solo tale riferimento.

Importante Per garantire la thread safety, il campo che contiene l'istanza dell'evento di EventRegistrationTokenTable<T> deve essere un campo a livello di classe. Se si tratta di un campo a livello di classe, il metodo GetOrCreateEventRegistrationTokenTable garantisce che quando più thread tentano di creare la tabella token, tutti i thread ottengono la stessa istanza della tabella. Per un determinato evento, tutte le chiamate al metodo GetOrCreateEventRegistrationTokenTable devono usare lo stesso campo a livello di classe.

La chiamata al metodo GetOrCreateEventRegistrationTokenTable nella funzione di accesso remove e nel metodo RaiseEvent (il metodo OnRaiseEvent in C#) garantisce che non si verifichino eccezioni se questi metodi vengono chiamati prima dell'aggiunta di delegati del gestore eventi.

Gli altri membri della classe EventRegistrationTokenTable<T> usati nel modello di evento UWP includono quanto segue:

  • Il metodo AddEventHandler genera un token per il delegato del gestore eventi, archivia il delegato nella tabella, lo aggiunge all'elenco chiamate e restituisce il token.

  • L'overload del metodo RemoveEventHandler(EventRegistrationToken) rimuove il delegato dalla tabella e dall'elenco chiamate.

    Nota I metodi AddEventHandler e RemoveEventHandler(EventRegistrationToken) bloccano la tabella per garantire la thread safety.

  • La proprietà InvocationList restituisce un delegato che include tutti i gestori eventi attualmente registrati per gestire l'evento. Utilizzare questo delegato per generare l'evento o usare i metodi della classe Delegate per richiamare i gestori singolarmente.

    Nota È consigliabile seguire il modello illustrato nell'esempio fornito in precedenza in questo articolo e copiare il delegato in una variabile temporanea prima di richiamarlo. In questo modo si evita una race condition in cui un thread rimuove l'ultimo gestore, riducendo il delegato su Null subito prima che un altro thread tenti di richiamare il delegato. I delegati non sono modificabili, quindi la copia è ancora valida.

Inserire il proprio codice nelle funzioni di accesso in base alle esigenze. Se il thread safety è un problema, è necessario fornire il proprio blocco per il codice.

Utenti C#: quando si scrivono funzioni di accesso agli eventi personalizzate nel modello di evento UWP, il compilatore non fornisce i soliti collegamenti sintattici. Genera errori se si usa il nome dell'evento nel codice.

Utenti di Visual Basic: in .NET, un evento è solo un delegato multicast che rappresenta tutti i gestori eventi registrati. La generazione dell'evento significa semplicemente richiamare il delegato. La sintassi di Visual Basic nasconde in genere le interazioni con il delegato e il compilatore copia il delegato prima di richiamarlo, come descritto nella nota sulla thread safety. Quando crei un evento personalizzato in un componente Windows Runtime, devi gestire direttamente il delegato. Ciò significa anche che è possibile, ad esempio, usare il metodo MulticastDelegate.GetInvocationList per ottenere una matrice che contiene un delegato separato per ogni gestore eventi, se si desidera richiamare i gestori separatamente.