C# Language
Wydarzenia
Szukaj…
Wprowadzenie
Zdarzenie to powiadomienie, że coś się wydarzyło (na przykład kliknięcie myszą) lub, w niektórych przypadkach, nastąpi (np. Zmiana ceny).
Klasy mogą definiować zdarzenia, a ich wystąpienia (obiekty) mogą wywoływać te zdarzenia. Na przykład przycisk może zawierać zdarzenie Click, które jest wywoływane, gdy użytkownik go kliknie.
Procedury obsługi zdarzeń są wówczas metodami wywoływanymi po wywołaniu odpowiadającego im zdarzenia. Formularz może na przykład zawierać moduł obsługi zdarzeń Kliknięcie dla każdego przycisku, który zawiera.
Parametry
Parametr | Detale |
---|---|
EventArgsT | Typ wywodzący się z EventArgs i zawierający parametry zdarzenia. |
Nazwa wydarzenia | Nazwa wydarzenia |
Nazwa handlowca | Nazwa modułu obsługi zdarzeń. |
SenderObject | Obiekt wywołujący zdarzenie. |
EventArguments | Instancja typu EventArgsT, która zawiera parametry zdarzenia. |
Uwagi
Podczas podnoszenia wydarzenia:
- Zawsze sprawdź, czy pełnomocnik ma
null
. Brak delegata oznacza, że wydarzenie nie ma subskrybentów. Zgłoszenie zdarzenia bez abonentów spowodujeNullReferenceException
.
- Skopiuj delegata (np.
EventName
) do zmiennej lokalnej (np.eventName
) przed sprawdzeniem, czy null / podnoszenie zdarzenia. Pozwala to uniknąć warunków wyścigu w środowiskach wielowątkowych:
Źle :
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.
Po prawej :
// 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);
- Użyj operatora warunkowego null (?.) Do podniesienia metody zamiast sprawdzania null delegata dla subskrybentów w instrukcji
if
:EventName?.Invoke(SenderObject, new EventArgsT());
- Podczas używania akcji <> do deklarowania typów delegowanych, anonimowy podpis metody / procedury obsługi zdarzeń musi być taki sam, jak deklarowany anonimowy typ delegowanego w deklaracji zdarzenia.
Deklarowanie i zgłaszanie zdarzeń
Deklarowanie zdarzenia
Możesz zadeklarować zdarzenie w dowolnej class
lub struct
używając następującej składni:
public class MyClass
{
// Declares the event for MyClass
public event EventHandler MyEvent;
// Raises the MyEvent event
public void RaiseEvent()
{
OnMyEvent();
}
}
Istnieje rozszerzona składnia do deklarowania zdarzeń, w której przechowujesz prywatną instancję zdarzenia i definiujesz instancję publiczną za pomocą add
i set
akcesoriów. Składnia jest bardzo podobna do właściwości C #. We wszystkich przypadkach wskazana powyżej składnia powinna być preferowana, ponieważ kompilator emituje kod, aby zapewnić, że wiele wątków może bezpiecznie dodawać i usuwać programy obsługi zdarzeń do zdarzenia w klasie.
Podnoszenie wydarzenia
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);
}
Pamiętaj, że zdarzenia mogą być wywoływane tylko przez typ deklarujący. Klienci mogą tylko subskrybować / anulować subskrypcję.
W przypadku wersji C # wcześniejszych niż 6.0, gdzie EventName?.Invoke
nie jest obsługiwane, dobrą praktyką jest przypisanie zdarzenia do zmiennej tymczasowej przed wywołaniem, jak pokazano w przykładzie, co zapewnia bezpieczeństwo wątków w przypadkach, gdy wiele wątków wykonuje to samo kod. Niezastosowanie się do tego może spowodować NullReferenceException
w niektórych przypadkach, gdy wiele wątków używa tej samej instancji obiektu. W C # 6.0 kompilator emituje kod podobny do tego pokazanego w przykładzie kodu dla C # 6.
Standardowa deklaracja zdarzenia
Deklaracja zdarzenia:
public event EventHandler<EventArgsT> EventName;
Deklaracja modułu obsługi zdarzeń:
public void HandlerName(object sender, EventArgsT args) { /* Handler logic */ }
Subskrybowanie wydarzenia:
Dynamicznie:
EventName += HandlerName;
Poprzez projektanta:
- Kliknij przycisk Zdarzenia w oknie właściwości kontrolki (błyskawica)
- Kliknij dwukrotnie nazwę zdarzenia:
- Visual Studio wygeneruje kod zdarzenia:
private void Form1_Load(object sender, EventArgs e)
{
}
Wywoływanie metody:
EventName(SenderObject, EventArguments);
Anonimowa deklaracja obsługi zdarzeń
Deklaracja zdarzenia:
public event EventHandler<EventArgsType> EventName;
Deklaracja procedury obsługi zdarzeń za pomocą operatora lambda => i subskrypcja zdarzenia:
EventName += (obj, eventArgs) => { /* Handler logic */ };
Deklaracja procedury obsługi zdarzeń przy użyciu składni metody anonimowego delegowania :
EventName += delegate(object obj, EventArgsType eventArgs) { /* Handler Logic */ };
Deklaracja i subskrypcja procedury obsługi zdarzenia, która nie korzysta z parametru zdarzenia, a zatem może korzystać z powyższej składni bez konieczności określania parametrów:
EventName += delegate { /* Handler Logic */ }
Wywoływanie wydarzenia:
EventName?.Invoke(SenderObject, EventArguments);
Niestandardowa deklaracja zdarzenia
Zdarzenia mogą być dowolnego typu delegata, nie tylko EventHandler
i EventHandler<T>
. Na przykład:
//Declaring an event
public event Action<Param1Type, Param2Type, ...> EventName;
Jest to używane podobnie do standardowych zdarzeń EventHandler
:
//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, ...);
Możliwe jest zadeklarowanie wielu zdarzeń tego samego typu w pojedynczej instrukcji, podobnie jak w przypadku pól i zmiennych lokalnych (choć często jest to zły pomysł):
public event EventHandler Event1, Event2, Event3;
Ten deklaruje trzy oddzielne zdarzenia ( Event1
, Event2
i Event3
) wszystkie typu EventHandler
.
Uwaga: Chociaż niektóre kompilatory mogą akceptować tę składnię zarówno w interfejsach, jak i klasach, specyfikacja C # (v5.0 §13.2.3) zapewnia gramatykę interfejsów, która na to nie pozwala, więc używanie jej w interfejsach może być zawodne w przypadku różnych kompilatorów.
Tworzenie niestandardowych elementów EventArg zawierających dodatkowe dane
Zdarzenia niestandardowe zwykle wymagają niestandardowych argumentów zdarzeń zawierających informacje o zdarzeniu. Na przykład MouseEventArgs
który jest używany przez zdarzenia myszy, takie jak MouseDown
lub MouseUp
, zawiera informacje o Location
lub Buttons
które posłużyły do wygenerowania zdarzenia.
Podczas tworzenia nowych zdarzeń, aby utworzyć niestandardowe arg zdarzeń:
- Utwórz klasę wywodzącą się z
EventArgs
i zdefiniuj właściwości niezbędnych danych. - Zgodnie z konwencją nazwa klasy powinna kończyć się na
EventArgs
.
Przykład
W poniższym przykładzie tworzymy zdarzenie PriceChangingEventArgs
dla właściwości Price
klasy. Klasa danych zdarzenia zawiera CurrentPrice
i NewPrice
. Zdarzenie powstaje, gdy przypisujesz nową wartość do właściwości Price
i informuje konsumenta, że wartość się zmienia, i informuje go o aktualnej cenie i nowej cenie:
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; }
}
Produkt
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);
}
}
Możesz ulepszyć przykład, pozwalając konsumentowi zmienić nową wartość, a następnie wartość zostanie użyta dla właściwości. Aby to zrobić, wystarczy zastosować te zmiany w klasach.
Zmień definicję NewPrice
aby była NewPrice
do ustawienia:
public int NewPrice { get; set; }
Zmień definicję Price
aby użyć e.NewPrice
jako wartości właściwości, po wywołaniu OnPriceChanging
:
int price;
public int Price
{
get { return price; }
set
{
var e = new PriceChangingEventArgs(price, value);
OnPriceChanging(e);
price = e.NewPrice;
}
}
Tworzenie zdarzenia, które można anulować
Zdarzenie, które może zostać anulowane, może zostać podniesione przez klasę, gdy ma zamiar wykonać akcję, którą można anulować, na przykład zdarzenie FormClosing
Form
.
Aby utworzyć takie wydarzenie:
- Utwórz nowy argument zdarzenia pochodzący z
CancelEventArgs
i dodaj dodatkowe właściwości danych zdarzenia. - Utwórz wydarzenie za pomocą
EventHandler<T>
i użyj nowej klasy arg anulowania zdarzenia, którą utworzyłeś.
Przykład
W poniższym przykładzie tworzymy zdarzenie PriceChangingEventArgs
dla właściwości Price
klasy. Klasa danych zdarzenia zawiera Value
która informuje konsumenta o nowej. Zdarzenie jest wywoływane, gdy przypisujesz nową wartość do właściwości Price
i informuje konsumenta, że wartość się zmienia, i pozwala mu anulować zdarzenie. Jeśli konsument anuluje wydarzenie, zostanie użyta poprzednia wartość Price
:
PriceChangingEventArgs
public class PriceChangingEventArgs : CancelEventArgs
{
int value;
public int Value
{
get { return value; }
}
public PriceChangingEventArgs(int value)
{
this.value = value;
}
}
Produkt
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);
}
}
Właściwości zdarzenia
Jeśli klasa podnosi dużą liczbę zdarzeń, koszt przechowywania jednego pola na jednego uczestnika może być nie do przyjęcia. .NET Framework zapewnia właściwości zdarzeń dla tych przypadków. W ten sposób możesz użyć innej struktury danych, takiej jak EventHandlerList
do przechowywania delegatów zdarzeń:
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);
}
}
Takie podejście jest szeroko stosowane w ramach GUI, takich jak WinForm, w których formanty mogą mieć dziesiątki, a nawet setki zdarzeń.
Zauważ, że EventHandlerList
nie jest bezpieczny dla wątków, więc jeśli spodziewasz się, że twoja klasa będzie używana z wielu wątków, musisz dodać instrukcje blokady lub inny mechanizm synchronizacji (lub użyć pamięci zapewniającej bezpieczeństwo wątków).