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 spowoduje NullReferenceException .
6.0
  • 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);
6.0
  • 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

6.0
private void OnMyEvent()
{
    EventName?.Invoke(this, EventArgs.Empty); 
}
6.0
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:

  1. Kliknij przycisk Zdarzenia w oknie właściwości kontrolki (błyskawica)
  2. Kliknij dwukrotnie nazwę zdarzenia:

wprowadź opis zdjęcia tutaj

  1. 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).



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow