Różnica między zdarzeniami i delegatami oraz ich odpowiednimi aplikacjami [zamknięte]

107

Nie widzę zalet używania wydarzeń nad delegatami, poza byciem cukrem syntaktycznym. Może nie rozumiem, ale wygląda na to, że wydarzenie jest tylko miejscem zastępczym dla delegata.

Czy mógłbyś wyjaśnić mi różnice i kiedy których użyć? Jakie są zalety i wady? Nasz kod jest mocno zakorzeniony w zdarzeniach i chcę dotrzeć do sedna tego.

Kiedy używałbyś delegatów do wydarzeń i odwrotnie? Proszę podać swoje rzeczywiste doświadczenia z obydwoma, powiedzmy w kodzie produkcji.

halfer
źródło
Tak, owinięcie głowy wokół różnic było naprawdę trudne, wyglądają tak samo i wydają się robić to samo na pierwszy rzut oka
Robert Gould
1
Zobacz także to pytanie .
Dimitri C.
1
Różnica między dwoma wydarzeniami i delegatami jest faktem, a nie opinią. Pytanie dotyczy odpowiednich aplikacji, ponieważ ilustrują one różnice w problemach, które rozwiązują technologie. To również nie jest kwestia opinii, ponieważ nikt nie pytał, co jest najlepsze. Żadna część tego pytania nie jest kwestią opinii, a to stwierdzenie również nie jest opinią. W mojej opinii. Czy dostałeś swoją odznakę?
Peter wygrał

Odpowiedzi:

49

Z technicznego punktu widzenia inne odpowiedzi dotyczyły różnic.

Z punktu widzenia semantyki zdarzenia to akcje wywoływane przez obiekt, gdy spełnione są określone warunki. Na przykład moja klasa Stock ma właściwość o nazwie Limit i wywołuje zdarzenie, gdy ceny akcji osiągną Limit. To powiadomienie odbywa się za pośrednictwem wydarzenia. Klasa właścicieli nie jest zainteresowana tym, czy ktoś naprawdę obchodzi to wydarzenie i się na nie zapisuje.

Delegat to bardziej ogólny termin opisujący konstrukcję podobną do wskaźnika w terminach C / C ++. Wszyscy delegaci w .Net są delegatami multiemisji. Z punktu widzenia semantyki są one zwykle używane jako rodzaj danych wejściowych. W szczególności są doskonałym sposobem na wdrożenie wzorca strategii . Na przykład, jeśli chcę posortować listę obiektów, mogę dostarczyć metodzie strategię komparatora, aby poinformować implementację, jak porównać dwa obiekty.

Użyłem tych dwóch metod w kodzie produkcyjnym. Mnóstwo moich obiektów danych powiadamia o spełnieniu określonych właściwości. Najbardziej podstawowy przykład, gdy zmienia się właściwość, wywoływane jest zdarzenie PropertyChanged (zobacz interfejs INotifyPropertyChanged). Użyłem delegatów w kodzie, aby zapewnić różne strategie przekształcania niektórych obiektów w ciąg. Ten konkretny przykład był gloryfikowaną listą implementacji ToString () dla określonego typu obiektu w celu wyświetlenia go użytkownikom.

Szymon Rozga
źródło
4
Może czegoś mi brakuje, ale czy Event Handler nie jest typem delegata?
Powerlord
1
Moja odpowiedź dotyczy pytań Edycja nr 1 i nr 2; różnice z punktu widzenia użytkowania. Na potrzeby tej dyskusji są one różne, chociaż z technicznego punktu widzenia masz rację. Spójrz na inne odpowiedzi dotyczące różnic technicznych.
Szymon Rozga
3
„Wszyscy delegaci w .Net są delegatami multiemisji”? Nawet delegaci, którzy zwracają wartości?
Qwertie
5
Tak. Historię znajdziesz pod adresem msdn.microsoft.com/en-us/magazine/cc301816.aspx . Sprawdź: msdn.microsoft.com/en-us/library/system.delegate.aspx . Jeśli zwracają wartości, zwracana wartość jest oceną ostatniego delegata w łańcuchu.
Szymon Rozga
delegaci są typami odwołań, które wskazują programy obsługi zdarzeń zdefiniowane w klasie subskrybenta. Innymi słowy, delegat jest używany jako łącze między zdarzeniem (w wydawcy) a obsługą zdarzenia zdefiniowaną w subskrybencie. W aplikacji będzie wielu subskrybentów, którzy będą musieli słuchać zdarzenia, a w takich scenariuszach delegaci oferują nam skuteczny sposób łączenia wydawcy i subskrybentów.
josepainumkal
55

Słowo kluczowe eventjest modyfikatorem zakresu dla delegatów multiemisji. Praktyczne różnice między tym a zwykłym deklarowaniem delegata multiemisji są następujące:

  • Możesz użyć eventw interfejsie.
  • Dostęp do wywołania delegata multiemisji jest ograniczony do klasy deklarującej. Zachowanie jest tak, jakby delegat był prywatny do wywołania. Dla celów przypisania dostęp jest określony przez jawny modyfikator dostępu (np public event.).

Interesujące jest zastosowanie +i -do delegatów multiemisji, a to jest podstawa składni +=i -=do przypisywania delegatów do zdarzeń. Te trzy fragmenty są równoważne:

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = B + C;

Próbka druga, ilustrująca przypisanie bezpośrednie i przypisanie kombinowane.

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = B;
A += C;

Próbka trzecia: bardziej znana składnia. Prawdopodobnie znasz przypisanie wartości null do usuwania wszystkich programów obsługi.

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = null;
A += B;
A += C;

Podobnie jak właściwości, zdarzenia mają pełną składnię, której nikt nigdy nie używa. To:

class myExample 
{
  internal EventHandler eh;

  public event EventHandler OnSubmit 
  { 
    add 
    {
      eh = Delegate.Combine(eh, value) as EventHandler;
    }
    remove
    {
      eh = Delegate.Remove(eh, value) as EventHandler;
    }
  }

  ...
}

... robi dokładnie to samo:

class myExample 
{
  public event EventHandler OnSubmit;
}

Metody add i remove są bardziej widoczne w raczej sztywnej składni używanej przez VB.NET (brak przeciążeń operatorów).

Peter Wone
źródło
6
+ dla „Dostęp wywołań do delegata multiemisji jest ograniczony do klasy deklarującej” - to jest dla mnie kluczowy punkt różnicy między delegatami a zdarzeniami.
RichardOD
2
Inną ważną różnicą (o której wspomniał itowlson poniżej) jest to, że nie można anulować subskrypcji wszystkich programów obsługi zdarzeń przez przypisanie ich do zdarzenia, ale można to zrobić z delegatem. (Swoją drogą, twoja była dla mnie najbardziej użyteczną odpowiedzią).
Roman Starkov
4
Chociaż Google i stackoverflow mogą być przydatne, wszystko to i wiele więcej jest dostępnych w oszałamiających szczegółach w specyfikacji języka C #, publicznie dostępnej bezpłatnie od firmy Microsoft. Wiem, że na
pozór
12

Wydarzenia to cukier syntaktyczny. Są pyszne. Kiedy widzę wydarzenie, wiem, co robić. Kiedy widzę delegata, nie jestem taki pewien.

Połączenie wydarzeń z interfejsami (więcej cukru) tworzy przepyszną przekąskę. Delegaci i czyste wirtualne klasy abstrakcyjne są znacznie mniej apetyczne.

Sean
źródło
ja też to widzę. Chcę głębszego i słodszego wyjaśnienia :)
13
Jednak za dużo cukru sprawia, że ​​jeden tłuszcz ... = P
Erik Forbes
5

Zdarzenia są oznaczone jako takie w metadanych. Dzięki temu elementy takie jak Windows Forms lub projektanci ASP.NET mogą odróżniać zdarzenia od zwykłych właściwości typu delegata i zapewniać im odpowiednią obsługę (w szczególności pokazując je na karcie Zdarzenia w oknie Właściwości).

Inną różnicą w stosunku do właściwości typu delegata jest to, że użytkownicy mogą tylko dodawać i usuwać programy obsługi zdarzeń, podczas gdy w przypadku właściwości typu delegata mogą ustawiać wartość:

someObj.SomeCallback = MyCallback;  // okay, replaces any existing callback
someObj.SomeEvent = MyHandler;  // not okay, must use += instead

Pomaga to wyodrębnić subskrybentów zdarzeń: mogę dodać moją procedurę obsługi do zdarzenia i możesz dodać swoją procedurę obsługi do tego samego zdarzenia i nie zastąpisz jej przypadkowo.

itowlson
źródło
4

Chociaż zdarzenia są zwykle implementowane z delegatami multiemisji, nie ma wymogu, aby były używane w taki sposób. Jeśli klasa ujawnia zdarzenie, oznacza to, że klasa ujawnia dwie metody. Ich znaczenie to w istocie:

  1. Oto delegat. Przywołaj go, gdy wydarzy się coś interesującego.
  2. Oto delegat. Powinieneś zniszczyć wszelkie odniesienia do niego tak szybko, jak to wygodne (i nie nazywaj tego już).

Najczęstszym sposobem obsługi zdarzenia przez klasę jest zdefiniowanie delegata multiemisji i dodanie / usunięcie delegatów, którzy zostaną przekazani do powyższych metod, ale nie ma wymogu, aby działały w ten sposób. Niestety, architektura zdarzenia nie robi pewnych rzeczy, które uczyniłyby alternatywne podejścia znacznie czystszymi (np. Metoda subskrypcji zwróciłaby MethodInvoker, który byłby zachowany przez subskrybenta; aby anulować subskrypcję zdarzenia, po prostu wywołaj zwróconą metodę), więc delegaci multiemisji są zdecydowanie najbardziej powszechnym podejściem.

supercat
źródło
4

aby zrozumieć różnice, spójrz na te 2 przykłady

Przykład z delegatami (w tym przypadku akcja jest rodzajem delegata, który nie zwraca wartości)

public class Animal
{
    public Action Run {get; set;}

    public void RaiseEvent()
    {
        if (Run != null)
        {
            Run();
        }
    }
}

aby użyć delegata, powinieneś zrobić coś takiego

Animale animal= new Animal();
animal.Run += () => Console.WriteLine("I'm running");
animal.Run += () => Console.WriteLine("I'm still running") ;
animal.RaiseEvent();

ten kod działa dobrze, ale możesz mieć słabe punkty.

Na przykład, jeśli to napiszę

animal.Run += () => Console.WriteLine("I'm running");
animal.Run += () => Console.WriteLine("I'm still running");
animal.Run = () => Console.WriteLine("I'm sleeping") ;

z ostatnim wierszem kodu nadpisałem poprzednie zachowania tylko z brakującym jednym +(użyłem +zamiast +=)

Innym słabym punktem jest to, że każda klasa, która używa twojej Animalklasy, może podbić RaiseEventpo prostu ją wywołując animal.RaiseEvent().

Aby uniknąć tych słabych punktów, możesz użyć eventsw języku C #.

Twoja klasa zwierząt zmieni się w ten sposób

public class ArgsSpecial :EventArgs
   {
        public ArgsSpecial (string val)
        {
            Operation=val;
        }

        public string Operation {get; set;}
   } 



 public class Animal
    {
       public event EventHandler<ArgsSpecial> Run = delegate{} //empty delegate. In this way you are sure that value is always != null because no one outside of the class can change it

       public void RaiseEvent()
       {  
          Run(this, new ArgsSpecial("Run faster"));
       }
    }

do wywoływania wydarzeń

 Animale animal= new Animal();
 animal.Run += (sender, e) => Console.WriteLine("I'm running. My value is {0}", e.Operation);
 animal.RaiseEvent();

Różnice:

  1. Nie używasz właściwości publicznej, ale pola publicznego (ze zdarzeniami kompilator chroni twoje pola przed niepożądanym dostępem)
  2. Nie można bezpośrednio przypisywać wydarzeń. W takim przypadku nie możesz zrobić poprzedniego błędu, który pokazałem, zastępując zachowanie.
  3. Nikt spoza Twojej klasy nie może podnieść wydarzenia.
  4. Zdarzenia mogą być zawarte w deklaracji interfejsu, a pole nie

notatki

EventHandler jest zadeklarowany jako następujący delegat:

public delegate void EventHandler (object sender, EventArgs e)

pobiera nadawcę (typu Object) i argumenty zdarzenia. Nadawca ma wartość null, jeśli pochodzi z metod statycznych.

Możesz użyć również EventHAndlerzamiast tego przykładu, który używaEventHandler<ArgsSpecial>

patrz tutaj dla dokumentacji o EventHandler

faby
źródło
3

Edytuj # 1 Kiedy używałbyś delegatów nad wydarzeniami i kontra odwrotnie? Proszę podać swoje rzeczywiste doświadczenia z obydwoma, powiedzmy w kodzie produkcji.

Projektując własne API, definiuję delegatów, które są przekazywane jako parametry do metod lub do konstruktorów klas:

  • Aby metoda mogła implementować prosty wzorzec „metody szablonu” (np. Delegaci Predicatei Actionsą przekazywane do klas kolekcji generycznych .Net)
  • Lub tak, aby klasa mogła wykonać „wywołanie zwrotne” (zazwyczaj wywołanie zwrotne do metody klasy, która ją utworzyła).

Te delegaty na ogół nie są opcjonalne w czasie wykonywania (tj. Nie mogą być null).

Zwykle nie używam wydarzeń; ale tam, gdzie używam zdarzeń, używam ich do opcjonalnego sygnalizowania zdarzeń do zera, jednego lub większej liczby klientów, którzy mogą być zainteresowani, tj. kiedy ma sens, aby klasa (np. System.Windows.Formklasa) istniała i była uruchamiana niezależnie od tego, czy klient ma dodał obsługę zdarzenia do swojego zdarzenia (np. istnieje zdarzenie „mouse down” formularza, ale jest opcjonalne, czy jakikolwiek klient zewnętrzny jest zainteresowany instalacją obsługi zdarzenia do tego zdarzenia).

ChrisW
źródło
2

Chociaż nie mam ku temu technicznych powodów, używam zdarzeń w kodzie w stylu interfejsu użytkownika, innymi słowy, na wyższych poziomach kodu i używam delegatów do logiki, która leży głębiej w kodzie. Jak mówię, możesz użyć jednego i drugiego, ale uważam, że ten wzorzec użycia jest logiczny, jeśli nic innego, pomaga udokumentować typy wywołań zwrotnych i ich hierarchię.


Edycja: Myślę, że różnica we wzorcach użytkowania byłaby taka, że ​​uważam, że ignorowanie zdarzeń jest całkowicie dopuszczalne, są to hooki / stuby, jeśli chcesz wiedzieć o zdarzeniu, posłuchaj ich, jeśli cię to nie obchodzi wydarzenie po prostu je zignoruj. Dlatego używam ich w interfejsie użytkownika, w stylu zdarzeń Javascript / Browser. Jednak kiedy mam delegata, NAPRAWDĘ oczekuję, że ktoś obsłuży zadanie delegata i wyrzuci wyjątek, jeśli nie zostanie obsłużony.

Robert Gould
źródło
Czy mógłbyś to rozwinąć, ponieważ używam również liczb parzystych w interfejsie użytkownika? Dobry przykład byłby wystarczający… dzięki
1

Różnica między wydarzeniami a delegatami jest o wiele mniejsza, niż sądziłem. Właśnie opublikowałem super krótki film na ten temat na YouTube: https://www.youtube.com/watch?v=el-kKK-7SBU

Mam nadzieję że to pomoże!

Pontus Wittenmark
źródło
2
Witamy w Stack Overflow! Chociaż może to teoretycznie odpowiedzieć na pytanie, lepiej byłoby zawrzeć tutaj zasadnicze części odpowiedzi i podać link do odniesienia.
GhostCat
1

Jeśli zamiast zdarzenia używamy tylko delegata, subskrybent ma możliwość sklonowania (), wywołania () samego delegata, jak pokazano poniżej na obrazku. Co nie jest w porządku.

wprowadź opis obrazu tutaj

To jest główna różnica b / w wydarzenie i delegata. abonent ma tylko jedno uprawnienie tj. słuchanie wydarzeń

Klasa ConsoleLog subskrybuje zdarzenia dziennika za pośrednictwem EventLogHandler

public class ConsoleLog
{
    public ConsoleLog(Operation operation)
    {
        operation.EventLogHandler += print;
    }

    public void print(string str)
    {
        Console.WriteLine("write on console : " + str);
    }
}

Klasa FileLog subskrybuje zdarzenia dziennika za pośrednictwem EventLogHandler

public class FileLog
{
    public FileLog(Operation operation)
    {
        operation.EventLogHandler += print;
    }

    public void print(string str)
    {
        Console.WriteLine("write in File : " + str);
    }
}

Klasa Operation publikuje zdarzenia w dzienniku

public delegate void logDelegate(string str);
public class Operation
{
    public event logDelegate EventLogHandler;
    public Operation()
    {
        new FileLog(this);
        new ConsoleLog(this);
    }

    public void DoWork()
    {
        EventLogHandler.Invoke("somthing is working");
    }
}
Narottam Goyal
źródło