C# Language
eventi
Ricerca…
introduzione
Un evento è una notifica che qualcosa si è verificato (come un clic del mouse) o, in alcuni casi, sta per verificarsi (come una variazione di prezzo).
Le classi possono definire eventi e le loro istanze (oggetti) possono generare questi eventi. Ad esempio, un pulsante può contenere un evento Click che viene generato quando un utente ha fatto clic su di esso.
I gestori di eventi sono quindi i metodi che vengono richiamati quando viene generato l'evento corrispondente. Un modulo può contenere un gestore di eventi Clicked per ogni pulsante che contiene, per esempio.
Parametri
Parametro | Dettagli |
---|---|
EventArgsT | Il tipo che deriva da EventArgs e contiene i parametri dell'evento. |
Nome dell'evento | Il nome dell'evento. |
handlerName | Il nome del gestore di eventi. |
SenderObject | L'oggetto che sta invocando l'evento. |
EventArguments | Un'istanza del tipo EventArgsT che contiene i parametri dell'evento. |
Osservazioni
Quando si alza un evento:
- Controllare sempre se il delegato è
null
. Un delegato nullo significa che l'evento non ha sottoscrittori. Aumentare un evento senza abbonati comporterà unaNullReferenceException
.
- Copia il delegato (ad es.
EventName
) in una variabile locale (ad es.eventName
) prima di verificare la presenza di null / raise dell'evento. Questo evita condizioni di gara in ambienti multi-thread:
Sbagliato :
if(Changed != null) // Changed has 1 subscriber at this point
// In another thread, that one subscriber decided to unsubscribe
Changed(this, args); // `Changed` is now null, `NullReferenceException` is thrown.
A destra :
// Cache the "Changed" event as a local. If it is not null, then use
// the LOCAL variable (handler) to raise the event, NOT the event itself.
var handler = Changed;
if(handler != null)
handler(this, args);
- Utilizzare l'operatore null-condizionale (?.) Per aumentare il metodo anziché il controllo null del delegato per i sottoscrittori in un'istruzione
if
:EventName?.Invoke(SenderObject, new EventArgsT());
- Quando si utilizza l'azione <> per dichiarare i tipi di delegato, la firma del gestore di un metodo / evento anonimo deve essere uguale al tipo di delegato anonimo dichiarato nella dichiarazione di evento.
Dichiarare e sollevare eventi
Dichiarazione di un evento
Puoi dichiarare un evento su qualsiasi class
o struct
usando la seguente sintassi:
public class MyClass
{
// Declares the event for MyClass
public event EventHandler MyEvent;
// Raises the MyEvent event
public void RaiseEvent()
{
OnMyEvent();
}
}
Esiste una sintassi espansa per la dichiarazione degli eventi, in cui si detiene un'istanza privata dell'evento e si definisce un'istanza pubblica utilizzando add
e set
accessors. La sintassi è molto simile alle proprietà C #. In tutti i casi, la sintassi sopra illustrata dovrebbe essere preferita, poiché il compilatore emette il codice per garantire che più thread possano aggiungere e rimuovere in modo sicuro i gestori di eventi all'evento della classe.
Alzare l'evento
private void OnMyEvent()
{
EventName?.Invoke(this, EventArgs.Empty);
}
private void OnMyEvent()
{
// Use a local for EventName, because another thread can modify the
// public EventName between when we check it for null, and when we
// raise the event.
var eventName = EventName;
// If eventName == null, then it means there are no event-subscribers,
// and therefore, we cannot raise the event.
if(eventName != null)
eventName(this, EventArgs.Empty);
}
Tieni presente che gli eventi possono essere generati solo dal tipo di dichiarazione. I clienti possono solo iscriversi / annullare l'iscrizione.
Per le versioni C # precedenti alla 6.0, dove EventName?.Invoke
non è supportato, è buona pratica assegnare l'evento a una variabile temporanea prima del richiamo, come mostrato nell'esempio, che garantisce la sicurezza del thread nei casi in cui più thread eseguono lo stesso codice. In caso NullReferenceException
possibile che venga generata una NullReferenceException
in alcuni casi in cui più thread utilizzano la stessa istanza di oggetto. In C # 6.0, il compilatore emette un codice simile a quello mostrato nell'esempio di codice per C # 6.
Dichiarazione di eventi standard
Dichiarazione di evento:
public event EventHandler<EventArgsT> EventName;
Dichiarazione del gestore eventi:
public void HandlerName(object sender, EventArgsT args) { /* Handler logic */ }
Iscrizione all'evento:
dinamicamente:
EventName += HandlerName;
Attraverso il Designer:
- Fare clic sul pulsante Eventi nella finestra delle proprietà del controllo (Lightning bolt)
- Fai doppio clic sul nome dell'evento:
- Visual Studio genererà il codice evento:
private void Form1_Load(object sender, EventArgs e)
{
}
Invocare il metodo:
EventName(SenderObject, EventArguments);
Dichiarazione del gestore eventi anonimo
Dichiarazione di evento:
public event EventHandler<EventArgsType> EventName;
Dichiarazione del gestore eventi che utilizza l' operatore lambda => e si iscrive all'evento:
EventName += (obj, eventArgs) => { /* Handler logic */ };
Dichiarazione del gestore eventi che utilizza la sintassi del metodo anonimo delegato :
EventName += delegate(object obj, EventArgsType eventArgs) { /* Handler Logic */ };
Dichiarazione e sottoscrizione di un gestore di eventi che non utilizza il parametro dell'evento e pertanto può utilizzare la sintassi precedente senza dover specificare i parametri:
EventName += delegate { /* Handler Logic */ }
Invocazione dell'evento:
EventName?.Invoke(SenderObject, EventArguments);
Dichiarazione di eventi non standard
Gli eventi possono essere di qualsiasi tipo di delegato, non solo EventHandler
e EventHandler<T>
. Per esempio:
//Declaring an event
public event Action<Param1Type, Param2Type, ...> EventName;
Questo è usato in modo simile agli eventi EventHandler
standard:
//Adding a named event handler
public void HandlerName(Param1Type parameter1, Param2Type parameter2, ...) {
/* Handler logic */
}
EventName += HandlerName;
//Adding an anonymous event handler
EventName += (parameter1, parameter2, ...) => { /* Handler Logic */ };
//Invoking the event
EventName(parameter1, parameter2, ...);
È possibile dichiarare più eventi dello stesso tipo in una singola istruzione, in modo simile ai campi e alle variabili locali (sebbene ciò possa spesso essere una cattiva idea):
public event EventHandler Event1, Event2, Event3;
Questo dichiara tre eventi separati ( Event1
, Event2
ed Event3
) tutti di tipo EventHandler
.
Nota: sebbene alcuni compilatori possano accettare questa sintassi sia nelle interfacce che nelle classi, la specifica C # (v5.0 §13.2.3) fornisce la grammatica per le interfacce che non lo consentono, quindi l'utilizzo di questo nelle interfacce potrebbe non essere affidabile con diversi compilatori.
Creazione di eventi personalizzati contenenti dati aggiuntivi
Gli eventi personalizzati di solito richiedono argomenti di eventi personalizzati contenenti informazioni sull'evento. Ad esempio MouseEventArgs
utilizzato dagli eventi del mouse come MouseDown
o MouseUp
, contiene informazioni sulla Location
o sui Buttons
utilizzati per generare l'evento.
Quando si creano nuovi eventi, per creare un evento personalizzato arg:
- Creare una classe derivante da
EventArgs
e definire le proprietà per i dati necessari. - Come convenzione, il nome della classe dovrebbe terminare con
EventArgs
.
Esempio
Nell'esempio seguente, creiamo un evento PriceChangingEventArgs
per la proprietà Price
di una classe. La classe di dati evento contiene un CurrentPrice
e NewPrice
. L'evento aumenta quando si assegna un nuovo valore alla proprietà Price
e si fa sapere al consumatore che il valore sta cambiando e consente loro di conoscere il prezzo corrente e il nuovo prezzo:
PriceChangingEventArgs
public class PriceChangingEventArgs : EventArgs
{
public PriceChangingEventArgs(int currentPrice, int newPrice)
{
this.CurrentPrice = currentPrice;
this.NewPrice = newPrice;
}
public int CurrentPrice { get; private set; }
public int NewPrice { get; private set; }
}
Prodotto
public class Product
{
public event EventHandler<PriceChangingEventArgs> PriceChanging;
int price;
public int Price
{
get { return price; }
set
{
var e = new PriceChangingEventArgs(price, value);
OnPriceChanging(e);
price = value;
}
}
protected void OnPriceChanging(PriceChangingEventArgs e)
{
var handler = PriceChanging;
if (handler != null)
handler(this, e);
}
}
È possibile migliorare l'esempio consentendo al consumatore di modificare il nuovo valore e quindi il valore verrà utilizzato per la proprietà. Per fare ciò è sufficiente applicare questi cambiamenti nelle classi.
Modifica la definizione di NewPrice
per essere impostabile:
public int NewPrice { get; set; }
Modifica la definizione di Price
per utilizzare e.NewPrice
come valore della proprietà, dopo aver chiamato OnPriceChanging
:
int price;
public int Price
{
get { return price; }
set
{
var e = new PriceChangingEventArgs(price, value);
OnPriceChanging(e);
price = e.NewPrice;
}
}
Creare un evento cancellabile
Un evento cancellabile può essere generato da una classe quando sta per eseguire un'azione che può essere annullata, come ad esempio l'evento FormClosing
di un Form
.
Per creare un evento del genere:
- Creare un nuovo argomento evento derivato da
CancelEventArgs
e aggiungere proprietà aggiuntive per i dati degli eventi. - Crea un evento utilizzando
EventHandler<T>
e utilizza la nuova classe di evento event cancel che hai creato.
Esempio
Nell'esempio seguente, creiamo un evento PriceChangingEventArgs
per la proprietà Price
di una classe. La classe di dati dell'evento contiene un Value
che consente al consumatore di conoscere il nuovo. L'evento aumenta quando si assegna un nuovo valore alla proprietà Price
e si consente al consumatore di sapere che il valore sta cambiando e di consentire loro di annullare l'evento. Se il consumatore annulla l'evento, verrà utilizzato il valore precedente per Price
:
PriceChangingEventArgs
public class PriceChangingEventArgs : CancelEventArgs
{
int value;
public int Value
{
get { return value; }
}
public PriceChangingEventArgs(int value)
{
this.value = value;
}
}
Prodotto
public class Product
{
int price;
public int Price
{
get { return price; }
set
{
var e = new PriceChangingEventArgs(value);
OnPriceChanging(e);
if (!e.Cancel)
price = value;
}
}
public event EventHandler<PriceChangingEventArgs> PropertyChanging;
protected void OnPriceChanging(PriceChangingEventArgs e)
{
var handler = PropertyChanging;
if (handler != null)
PropertyChanging(this, e);
}
}
Proprietà dell'evento
Se una classe aumenta di molto il numero di eventi, il costo di archiviazione di un campo per delegato potrebbe non essere accettabile. .NET Framework fornisce le proprietà degli eventi per questi casi. In questo modo è possibile utilizzare un'altra struttura dati come EventHandlerList
per memorizzare i delegati dell'evento:
public class SampleClass
{
// Define the delegate collection.
protected EventHandlerList eventDelegates = new EventHandlerList();
// Define a unique key for each event.
static readonly object someEventKey = new object();
// Define the SomeEvent event property.
public event EventHandler SomeEvent
{
add
{
// Add the input delegate to the collection.
eventDelegates.AddHandler(someEventKey, value);
}
remove
{
// Remove the input delegate from the collection.
eventDelegates.RemoveHandler(someEventKey, value);
}
}
// Raise the event with the delegate specified by someEventKey
protected void OnSomeEvent(EventArgs e)
{
var handler = (EventHandler)eventDelegates[someEventKey];
if (handler != null)
handler(this, e);
}
}
Questo approccio è ampiamente utilizzato in framework GUI come WinForms in cui i controlli possono avere dozzine e persino centinaia di eventi.
Notare che EventHandlerList
non è thread-safe, quindi se si prevede che la classe venga utilizzata da più thread, sarà necessario aggiungere istruzioni di blocco o altri meccanismi di sincronizzazione (o utilizzare una memoria che fornisce sicurezza di thread).