C #: Podnoszenie dziedziczonego zdarzenia

144

Mam klasę bazową, która zawiera następujące zdarzenia:

public event EventHandler Loading;
public event EventHandler Finished;

W klasie, która dziedziczy z tej klasy bazowej, próbuję podnieść zdarzenie:

this.Loading(this, new EventHandler()); // All we care about is which object is loading.

Otrzymuję następujący błąd:

Zdarzenie „BaseClass.Loading” może pojawić się tylko po lewej stronie + = lub - = (BaseClass ')

Zakładam, że nie mogę uzyskać dostępu do tych wydarzeń tak samo, jak inni odziedziczeni członkowie?

jwarzech
źródło
Na to pytanie odpowiedział już Frederik Gheysels . Ta sama praktyka jest zalecana w Microsoft Docs: Jak podnosić zdarzenia klasy bazowej w klasach pochodnych (Przewodnik programowania w języku C #) jako oficjalny „Przewodnik programowania w języku C #”
Eliahu Aaron

Odpowiedzi:

160

Co musisz zrobić, to:

W swojej klasie bazowej (w której zadeklarowałeś zdarzenia) utwórz chronione metody, których można użyć do zgłaszania zdarzeń:

public class MyClass
{
   public event EventHandler Loading;
   public event EventHandler Finished;

   protected virtual void OnLoading(EventArgs e)
   {
       EventHandler handler = Loading;
       if( handler != null )
       {
           handler(this, e);
       }
   }

   protected virtual void OnFinished(EventArgs e)
   {
       EventHandler handler = Finished;
       if( handler != null )
       {
           handler(this, e);
       }
   }
}

(Zwróć uwagę, że prawdopodobnie powinieneś zmienić te metody, aby sprawdzić, czy musisz wywołać eventhandler, czy nie).

Następnie w klasach dziedziczących z tej klasy bazowej można po prostu wywołać metody OnFinished lub OnLoading, aby zgłosić zdarzenia:

public AnotherClass : MyClass
{
    public void DoSomeStuff()
    {
        ...
        OnLoading(EventArgs.Empty);
        ...
        OnFinished(EventArgs.Empty);
    }
}
Frederik Gheysels
źródło
5
Te metody powinny być chronione wirtualnie, chyba że istnieje jakiś powód, aby postąpić inaczej.
Max Schmeling
6
Dlaczego miałby być wirtualny? Zadeklarowałbym to wirtualnie, gdybym chciał, aby spadkobiercy zmienili sposób, w jaki wydarzenie powinno zostać wywołane, ale w większości przypadków nie widzę powodu, aby to robić ...
Frederik Gheysels
4
Oficjalne wytyczne: msdn.microsoft.com/en-us/library/w369ty8x(VS.80).aspx
meandmycode
5
Jeśli chodzi o uczynienie metody wirtualną, aby dziedziczący mogli przesłonić zachowanie wywołania zdarzenia: Ile razy byłeś w sytuacji, w której było to konieczne? Obok tego; w metodzie nadpisanej nie możesz zgłosić zdarzenia, ponieważ otrzymasz ten sam błąd, co ten wspomniany przez TS.
Frederik Gheysels
2
@Verax Podążam za tym, ponieważ rozumowanie jest rozsądne, w zależności od tego, jak wielokrotnego użytku i rozszerzalności oczekuję, że kod będzie, podałem oficjalne wytyczne, aby to poprzeć ... w momencie pisania wydawałeś się również bardzo nowy w .NET Verax więc jest trochę kłopotliwe, że wykopałeś to, aby nie zgodzić się z moim 7-letnim doświadczeniem
meandmycode
123

Dostęp do zdarzenia można uzyskać tylko w klasie deklarującej, ponieważ .NET tworzy prywatne zmienne instancji za kulisami, które faktycznie przechowują delegata. Robiąc to..

public event EventHandler MyPropertyChanged;

faktycznie to robi;

private EventHandler myPropertyChangedDelegate;

public event EventHandler MyPropertyChanged
{
    add { myPropertyChangedDelegate += value; }
    remove { myPropertyChangedDelegate -= value; }
}

i robiąc to ...

MyPropertyChanged(this, EventArgs.Empty);

czy właściwie to ...

myPropertyChangedDelegate(this, EventArgs.Empty);

Więc możesz (oczywiście) uzyskać dostęp do zmiennej instancji delegata prywatnego tylko z poziomu klasy deklarującej.

Konwencją jest zapewnienie czegoś takiego w klasie deklarującej.

protected virtual void OnMyPropertyChanged(EventArgs e)
{
    EventHandler invoker = MyPropertyChanged;

    if(invoker != null) invoker(this, e);
}

Następnie możesz OnMyPropertyChanged(EventArgs.Empty)wywołać zdarzenie z dowolnego miejsca w tej klasie lub poniżej hierarchii dziedziczenia.

Adam Robinson
źródło
Miałem problemy z implementacją tego ... stackoverflow.com/q/10593632/328397
goodguys_activate
Wolę tę odpowiedź, ponieważ wyjaśnia, DLACZEGO należy zastosować to podejście, a nie tylko podejście. Dobra robota.
cowboydan
8

Zakładam, że nie mogę uzyskać dostępu do tych wydarzeń tak samo, jak inni odziedziczeni członkowie?

Dokładnie. Zwykle zapewnia się chronioną funkcję OnXyzlub RaiseXyzdla każdego zdarzenia w klasie bazowej, aby umożliwić podnoszenie z klas dziedziczonych. Na przykład:

public event EventHandler Loading;

protected virtual void OnLoading() {
    EventHandler handler = Loading;
    if (handler != null)
        handler(this, EventArgs.Empty);
}

Wywołane w odziedziczonej klasie:

OnLoading();
Konrad Rudolph
źródło
0

Możesz spróbować w ten sposób, to działa dla mnie:

public delegate void MyEventHaldler(object sender, EventArgs e);

public class B
{
    public virtual event MyEventHaldler MyEvent;
    protected override void OnChanged(EventArgs e)
    {
        if (MyEvent != null)
            MyEvent(this, e);
    }
}

public class D : B
{
    public override event MyEventHaldler MyEvent;
    protected override void OnChanged(EventArgs e)
    {
        if (MyEvent != null)
            MyEvent(this, e);
    }
}
Olioul Islam Rahi
źródło
2
Zwróć uwagę, że ten artykuł ostrzega przed deklarowaniem zdarzeń wirtualnych i zastępowaniem ich, ponieważ twierdzi, że kompilator nie obsługuje tego poprawnie: msdn.microsoft.com/en-us/library/hy3sefw3.aspx
public wireless
0

nie po to, żeby wskrzeszać stary wątek, ale na wypadek, gdyby ktoś patrzył, to co zrobiłem

protected EventHandler myPropertyChangedDelegate;

public event EventHandler MyPropertyChanged
{
    add { myPropertyChangedDelegate += value; }
    remove { myPropertyChangedDelegate -= value; }
}

Umożliwia to dziedziczenie zdarzenia w klasie pochodnej, dzięki czemu można je wywołać bez konieczności zawijania metody, zachowując składnię + =. Myślę, że nadal możesz to zrobić za pomocą metod owijania, jeśli tak

public event EventHandler MyPropertyChanged
{
   add { AddDelegate(value); }
   remove { RemoveDelegate(value); }
}
Zeraphil
źródło