Gestire e generare eventi

Gli eventi in .NET si basano sul modello di delegato. Il modello di delegato segue lo schema progettuale osservatore, che consente a un sottoscrittore di effettuare la registrazione con e ricevere notifiche da un provider. Un mittente dell'evento esegue il push di una notifica di evento, mentre un ricevitore di eventi riceve la notifica e definisce una risposta. In questo articolo viene descritto come implementare gli eventi nel codice, come usare gli eventi nelle applicazioni e i componenti principali del modello di delegato.

evento

Un evento è un messaggio inviato da un oggetto per segnalare l'occorrenza di un'azione. L'azione può essere causata dall'interazione dell'utente, ad esempio un clic su un pulsante, oppure può derivare da un'altra logica del programma, ad esempio la modifica del valore di una proprietà. L'oggetto che genera l'evento viene chiamato mittente dell'evento. Il mente dell'evento non sa quale oggetto o metodo riceverà (handle) gli eventi che egli genera. L'evento è in genere un membro del mittente dell'evento. Ad esempio, l'evento Click è un membro della classe Button e l'evento PropertyChanged è un membro della classe che implementa l'interfaccia INotifyPropertyChanged.

Per definire un evento, usare la parola chiave event (in C#) o Event (in Visual Basic) nella firma della classe dell'evento e specificare il tipo di delegato per l'evento. I delegati vengono descritti nella sezione successiva.

In genere, per generare un evento, aggiungere un metodo contrassegnato come protected e virtual (in C#) o Protected e Overridable (in Visual Basic). Denominare il metodo OnNomeEvento; ad esempio, OnDataReceived. Il metodo deve accettare un parametro che specifica un oggetto dati dell'evento, ovvero un oggetto di tipo EventArgs o un tipo derivato. Fornire questo metodo per consentire alle classi derivate di sostituire la logica per la generazione dell'evento. Affinché l'evento sia ricevuto da delegati registrati, occorre che tramite una classe derivata venga sempre chiamato il metodo OnNomeEvento della classe di base.

Nell'esempio riportato di seguito viene illustrato come dichiarare un evento denominato ThresholdReached. L'evento è associato al delegatoEventHandler e generato in un metodo denominato OnThresholdReached.

class Counter
{
    public event EventHandler ThresholdReached;

    protected virtual void OnThresholdReached(EventArgs e)
    {
        EventHandler handler = ThresholdReached;
        handler?.Invoke(this, e);
    }

    // provide remaining implementation for the class
}
Public Class Counter
    Public Event ThresholdReached As EventHandler

    Protected Overridable Sub OnThresholdReached(e As EventArgs)
        RaiseEvent ThresholdReached(Me, e)
    End Sub

    ' provide remaining implementation for the class
End Class

Delegati

Un delegato è un tipo che contiene un riferimento a un metodo. Un delegato è dichiarato con una firma che mostra il tipo restituito e i parametri per i metodi a cui fa riferimento e può contenere riferimenti solo ai metodi che corrispondono alla firma. Un delegato è pertanto equivalente a un puntatore a funzione indipendente dai tipi o un callback. Una dichiarazione di delegato è sufficiente per definire una classe delegata.

Sono molti gli usi dei delegati in .NET. Nel contesto degli eventi, un delegato è un intermediario (o meccanismo di tipo puntatore) tra l'origine evento e il codice che gestisce l'evento. Associare un delegato a un evento, incluso il tipo di delegato nella dichiarazione di evento, come illustrato nell'esempio nella sezione precedente. Per altre informazioni sui delegati, vedere la classe Delegate.

.NET fornisce i delegati EventHandler e EventHandler<TEventArgs> per supportare la maggior parte degli scenari per gli eventi. Usare il delegato EventHandler per tutti gli eventi che non includono dati dell'evento. Usare il delegato EventHandler<TEventArgs> per eventi che includono dati sull'evento. Questi delegati non hanno alcun valore di tipo restituito e accettano due parametri (un oggetto per l'origine dell'evento e un oggetto per i dati dell'evento).

I delegati sono multicast, il che significa che possono mantenere riferimenti a più di un metodo di gestione degli eventi. Per informazioni dettagliate, vedere la pagina di riferimento per Delegate. I delegati offrono flessibilità e controlli specifici nella gestione degli eventi. Un delegato agisce come un dispatcher di eventi per la classe che genera l'evento compilando un elenco di gestori eventi registrati per l'evento.

Per gli scenari in cui i delegati EventHandler e EventHandler<TEventArgs> non sono appropriati, è possibile definire un delegato. Gli scenari che richiedono di definire un delegato sono molto rari, ad esempio quando è necessario lavorare con un codice che non riconosce i generics. Nella dichiarazione contrassegnare un delegato con la parola chiave delegate (in C#) o Delegate (in Visual Basic). Nell'esempio riportato di seguito viene illustrato come dichiarare un delegato denominato ThresholdReachedEventHandler.

public delegate void ThresholdReachedEventHandler(object sender, ThresholdReachedEventArgs e);
Public Delegate Sub ThresholdReachedEventHandler(sender As Object, e As ThresholdReachedEventArgs)

Dati dell'evento

I dati associati a un evento possono essere forniti tramite una classe di dati eventi. .NET fornisce molte classi di dati eventi che possono essere usate nelle applicazioni. Ad esempio, la classe SerialDataReceivedEventArgs è la classe di dati eventi per l'evento SerialPort.DataReceived. .NET segue un modello di denominazione che prevede di terminare tutte le classi di dati eventi con EventArgs. È possibile determinare quale classe di dati eventi è associata a un evento esaminando il delegato per tale evento. Ad esempio, il delegato SerialDataReceivedEventHandler include la classe SerialDataReceivedEventArgs come uno dei relativi parametri.

La classe EventArgs è il tipo base per tutte le classi di dati eventi. EventArgs è anche la classe usata quando a un evento non sono stati associati dati. Quando si crea un evento il cui solo scopo è quello di segnalare ad altre classi che si è verificato qualcosa e non è necessario passare i dati, includere la classe EventArgs come secondo parametro nel delegato. È possibile passare il valore EventArgs.Empty quando non viene fornito alcun dato. Il delegato EventHandler include la classe EventArgs come parametro.

Quando si vuole creare una classe di dati eventi personalizzata, creare una classe che deriva da EventArgs e quindi specificare i membri necessari per passare i dati correlati all'evento. In genere, è necessario usare lo stesso modello di denominazione di .NET e terminare il nome della classe di dati eventi con EventArgs.

Nell'esempio seguente viene illustrata una classe di dati eventi denominata ThresholdReachedEventArgs. Contiene le proprietà specifiche per l'evento generato.

public class ThresholdReachedEventArgs : EventArgs
{
    public int Threshold { get; set; }
    public DateTime TimeReached { get; set; }
}
Public Class ThresholdReachedEventArgs
    Inherits EventArgs

    Public Property Threshold As Integer
    Public Property TimeReached As DateTime
End Class

Gestori eventi

Per rispondere a un evento, definire un metodo del gestore eventi nel ricevitore di eventi. Questo metodo deve corrispondere alla firma del delegato per l'evento gestito. Nel gestore eventi eseguire le azioni necessarie quando viene generato l'evento, ad esempio la raccolta dell'input quando un utente fa clic su un pulsante. Per ricevere notifiche quando si verifica l'evento, il metodo del gestore eventi deve sottoscrivere l'evento.

Nell'esempio seguente viene illustrato un metodo del gestore eventi denominato c_ThresholdReached che corrisponde alla firma per il delegato EventHandler. Il metodo sottoscrive l'evento ThresholdReached.

class Program
{
    static void Main()
    {
        var c = new Counter();
        c.ThresholdReached += c_ThresholdReached;

        // provide remaining implementation for the class
    }

    static void c_ThresholdReached(object sender, EventArgs e)
    {
        Console.WriteLine("The threshold was reached.");
    }
}
Module Module1

    Sub Main()
        Dim c As New Counter()
        AddHandler c.ThresholdReached, AddressOf c_ThresholdReached

        ' provide remaining implementation for the class
    End Sub

    Sub c_ThresholdReached(sender As Object, e As EventArgs)
        Console.WriteLine("The threshold was reached.")
    End Sub
End Module

Gestori eventi statici e dinamici

.NET consente ai sottoscrittori di registrarsi per le notifiche degli eventi in modo statico o dinamico. I gestori eventi statici sono attivi per l'intera durata della classe di cui gestiscono gli eventi. I gestori eventi dinamici sono attivati e disattivati in modo esplicito durante l'esecuzione del programma, in genere in risposta a una logica di programma condizionale. Ad esempio, possono essere usati se le notifiche degli eventi sono necessarie solo in determinate condizioni oppure se un'applicazione fornisce più gestori eventi e le condizioni di runtime definiscono quello appropriato da usare. Nell'esempio della sezione precedente viene illustrato come aggiungere un gestore eventi in modo dinamico. Per altre informazioni, vedere Eventi (Visual Basic) e Eventi (Guida per programmatori C#).

Generazione di più eventi

Se la propria classe genera più eventi, il compilatore genera un campo per ogni istanza del delegato di evento. Se il numero di eventi è elevato, il costo di archiviazione di un campo per ciascun delegato non è sostenibile. Per questi casi, .NET fornisce le proprietà evento che è possibile usare con un'altra struttura di dati di propria scelta per archiviare i delegati degli eventi.

Le proprietà dell'evento sono costituite da dichiarazioni di eventi accompagnate dalle funzioni di accesso agli eventi. Queste funzioni sono metodi che consentono di aggiungere o rimuovere istanze del delegato di evento dalla struttura di dati di archiviazione. Si noti che le proprietà evento sono più lente rispetto ai campi evento, perché ogni delegato dell'evento deve essere recuperato prima di poter essere richiamato. Il compromesso è tra memoria e velocità. Se la classe definisce molti eventi che vengono generati di rado, sarà necessario implementare le proprietà degli eventi. Per altre informazioni, vedere Procedura: gestire più eventi mediante le relative proprietà.

Titolo Descrizione
Procedura: generare e utilizzare eventi Contiene esempi di generazione e uso di eventi.
Procedura: gestire più eventi mediante le relative proprietà Viene illustrato come usare le proprietà degli eventi per gestire più eventi.
Schema di progettazione osservatore Viene descritto lo schema progettuale che consente a un sottoscrittore di effettuare la registrazione con e ricevere notifiche da un provider.

Vedi anche