Event deklaracja dodatkowa warstwa abstrakcji i ochrony na delegata instancji. Ta ochrona zapobiega resetowaniu klientów delegata i jego listy wywołań przez klientów delegata i umożliwia jedynie dodawanie lub usuwanie celów z listy wywołań.
Jeśli oczywiście, ta warstwa ochronna również uniemożliwia „klientom” (kod spoza definiującej klasy / struktury) wywoływanie delegata i uzyskanie w jakikolwiek sposób obiektu delegowanego „za” zdarzeniem.
Jeppe Stig Nielsen
7
Nie do końca prawda. Możesz zadeklarować zdarzenie bez instancji delegowania zaplecza. W języku c # można jawnie zaimplementować zdarzenie i użyć innej wybranej struktury danych zaplecza.
Miguel Gamboa
3
@mmcdole, czy możesz podać przykład jego wyjaśnienia?
vivek nuna
103
Aby zrozumieć różnice, spójrz na 2 przykłady
Przykład z delegatami (w tym przypadku akcja - to rodzaj delegata, który nie zwraca wartości)
Animal animal=newAnimal();
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 napiszę to:
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 zastąpiłem poprzednie zachowania tylko jednym brakującym +(użyłem =zamiast +=)
Innym słabym punktem jest to, że każda klasa, która korzysta z twojej Animalklasy, może podbić RaiseEventtylko ją sprawdzając animal.RaiseEvent().
Aby uniknąć tych słabych punktów, możesz użyć eventsw c #.
Twoja klasa zwierząt zmieni się w ten sposób:
publicclassArgsSpecial:EventArgs{publicArgsSpecial(string val){Operation=val;}publicstringOperation{get;set;}}publicclassAnimal{// Empty delegate. In this way you are sure that value is always != null // because no one outside of the class can change it.publiceventEventHandler<ArgsSpecial>Run=delegate{}publicvoidRaiseEvent(){Run(this,newArgsSpecial("Run faster"));}}
wywoływać wydarzenia
Animal animal=newAnimal();
animal.Run+=(sender, e)=>Console.WriteLine("I'm running. My value is {0}", e.Operation);
animal.RaiseEvent();
Różnice:
Nie używasz własności publicznej, ale pola publicznego (używając zdarzeń, kompilator chroni twoje pola przed niepożądanym dostępem)
Wydarzenia nie mogą być przypisywane bezpośrednio. W takim przypadku nie spowoduje to pojawienia się poprzedniego błędu, który pokazałem przy nadpisywaniu zachowania.
Nikt spoza twojej klasy nie może podnieść tego wydarzenia.
Zdarzenia mogą być zawarte w deklaracji interfejsu, podczas gdy pole nie
Uwagi:
EventHandler jest zadeklarowany jako następujący delegat:
Wszystko wyglądało świetnie, dopóki nie natknąłem się na: „Nikt spoza twojej klasy nie jest w stanie podnieść tego wydarzenia”. Co to znaczy? Czy nikt nie może wywoływać, RaiseEventdopóki metoda wywołująca ma dostęp do wystąpienia animalw kodzie używającym zdarzenia?
dance2die
11
@Sung Wydarzenia mogą powstać tylko wewnątrz klasy, być może nie wyjaśniłem tego jasno. W przypadku zdarzeń można wywołać funkcję wywołującą zdarzenie (enkapsulację), ale można ją wywołać tylko z klasy definiującej ją. Daj mi znać, jeśli nie będę jasne.
@faby, masz na myśli, że chociaż wydarzenie zostało ogłoszone jako publiczne, nadal nie mogę tego zrobić animal.Run(this, new ArgsSpecial("Run faster");?
Pap.
1
@ChieltenBrinke Oczywiście zdarzenie może być przypisane w obrębie członków klasy ... ale nie inaczej.
Jim Balter
94
Oprócz właściwości składniowych i operacyjnych istnieje również różnica semantyczna.
Delegaci są koncepcyjnie szablonami funkcji; oznacza to, że wyrażają umowę, do której musi się stosować funkcja, aby można było uznać ją za „typ” delegata.
Wydarzenia reprezentują ... cóż, wydarzenia. Mają na celu ostrzec kogoś, gdy coś się wydarzy i tak, przestrzegają definicji delegata, ale to nie to samo.
Nawet jeśli byłyby dokładnie takie same (składniowe i w kodzie IL), nadal pozostanie różnica semantyczna. Ogólnie wolę mieć dwie różne nazwy dla dwóch różnych pojęć, nawet jeśli są one implementowane w ten sam sposób (co nie znaczy, że lubię mieć ten sam kod dwa razy).
Czy możemy więc powiedzieć, że wydarzenie to „specjalny” typ delegata?
Pap.
Nie rozumiem, o co ci chodzi. Możesz użyć delegata, aby „ostrzec kogoś, gdy coś się stanie”. Może byś tego nie zrobił, ale możesz i dlatego nie jest to nieodłączna właściwość zdarzenia.
steve
@Jorge Córdoba przykład delegata i wydarzenia delegat jest właścicielem gazety i wydarzeń (Subskrybuj lub anuluj subskrypcję), a niektórzy ludzie kupują gazetę, a niektórzy nie kupują gazety, co oznacza, że właściciel gazety nie może zmusić każdej osoby do zakupu gazety prawda czy fałsz?
W skrócie, odejmij od artykułu - Wydarzenia są enkapsulacją nad delegatami.
Cytat z artykułu:
Załóżmy, że zdarzenia nie istniały jako koncepcja w języku C # / .NET. W jaki sposób inna klasa zasubskrybuje wydarzenie? Trzy opcje:
Zmienna delegata publicznego
Zmienna delegowana wspierana przez właściwość
Zmienna delegowana za pomocą metod AddXXXHandler i RemoveXXXHandler
Opcja 1 jest wyraźnie okropna, ze wszystkich normalnych powodów, których nie znosimy zmiennych publicznych.
Opcja 2 jest nieco lepsza, ale umożliwia subskrybentom skuteczne wzajemne zastępowanie - byłoby zbyt łatwo napisać someInstance.MyEvent = eventHandler; które zastąpiłyby istniejące programy obsługi zdarzeń zamiast dodawać nowe. Ponadto nadal musisz napisać właściwości.
Opcja 3 jest w zasadzie tym, co dają ci wydarzenia, ale z gwarantowaną konwencją (generowaną przez kompilator i wspieraną przez dodatkowe flagi w IL) i „darmową” implementacją, jeśli jesteś zadowolony z semantyki, którą dają ci zdarzenia podobne do pól. Subskrybowanie i wypisywanie się z wydarzeń jest hermetyzowane bez umożliwienia arbitralnego dostępu do listy procedur obsługi zdarzeń, a języki mogą uprościć sprawę, zapewniając składnię zarówno deklaracji, jak i subskrypcji.
Jest to bardziej kwestia teoretyczna niż cokolwiek innego, ale FWIW zawsze czułem, że argument „Opcja 1 jest zła, ponieważ nie lubimy zmiennych publicznych” może nieco bardziej wyjaśnić. Jeśli on mówi, że ponieważ jest to „złe praktyki OOP”, techniczniepublic Delegate zmienna będzie wystawienie „dane”, ale do mojej najlepszej OOP wiedzy nigdy wspomniane koncepcje zupełnie jak Delegate(jest to ani „obiektem”, ani „komunikat”) , a .NET tak naprawdę ledwo traktuje delegatów jak dane.
jrh
Chociaż chciałbym również udzielić bardziej praktycznych porad, jeśli jesteś w sytuacji, w której chciałbyś upewnić się, że jest tylko jeden moduł obsługi, dobrym rozwiązaniem może być tworzenie własnych AddXXXHandlermetod za pomocą private Delegatezmiennej. W takim przypadku możesz sprawdzić, czy moduł obsługi jest już ustawiony i odpowiednio zareagować. Może to być również dobre ustawienie, jeśli potrzebujesz obiektu trzymającego, Delegateaby móc wyczyścić wszystkie programy obsługi ( eventnie daje ci to żadnej możliwości).
jrh
7
UWAGA: Jeśli masz dostęp do wersji C # 5.0 Unleashed , przeczytaj „Ograniczenia w zwykłym korzystaniu z usług delegatów” w rozdziale 18, zatytułowanym „Wydarzenia”, aby lepiej zrozumieć różnice między nimi.
Zawsze pomaga mi mieć prosty, konkretny przykład. Oto jeden dla społeczności. Najpierw pokażę, w jaki sposób możesz wykorzystać delegatów samodzielnie, aby zrobić to, co dla nas robią Wydarzenia. Następnie pokazuję, jak to samo rozwiązanie działałoby z instancją EventHandler. A potem wyjaśniam, dlaczego NIE chcemy robić tego, co wyjaśnię w pierwszym przykładzie. Ten post został zainspirowany artykułem Johna Skeeta.
Przykład 1: Korzystanie z publicznego delegata
Załóżmy, że mam aplikację WinForms z jednym rozwijanym polem. Lista rozwijana jest powiązana z List<Person>. Gdzie osoba ma właściwości Id, Name, NickName, HairColor. W formularzu głównym znajduje się niestandardowy element sterujący użytkownika, który pokazuje właściwości tej osoby. Gdy ktoś wybierze osobę z listy rozwijanej, etykiety w aktualizacji kontroli użytkownika pokazują właściwości wybranej osoby.
Oto jak to działa. Mamy trzy pliki, które pomagają nam to połączyć:
Mediator.cs - klasa statyczna przechowuje delegatów
Form1.cs - główny formularz
DetailView.cs - kontrola użytkownika pokazuje wszystkie szczegóły
Oto odpowiedni kod dla każdej z klas:
classMediator{publicdelegatevoidPersonChangedDelegate(Person p);//delegate type definitionpublicstaticPersonChangedDelegatePersonChangedDel;//delegate instance. Detail view will "subscribe" to this.publicstaticvoidOnPersonChanged(Person p)//Form1 will call this when the drop-down changes.{if(PersonChangedDel!=null){PersonChangedDel(p);}}}
Wreszcie mamy następujący kod w naszym Form1.cs. Tutaj nazywamy się OnPersonChanged, który wywołuje dowolny kod subskrybowany uczestnikowi.
privatevoid comboBox1_SelectedIndexChanged(object sender,EventArgs e){Mediator.OnPersonChanged((Person)comboBox1.SelectedItem);//Call the mediator's OnPersonChanged method. This will in turn call all the methods assigned (i.e. subscribed to) to the delegate -- in this case `DetailView_PersonChanged`.}
Ok. W ten sposób działałoby to bez użycia wydarzeń i po prostu przy użyciu delegatów . Po prostu umieszczamy delegata publicznego na zajęciach - możesz ustawić go jako statyczny, singleton, czy cokolwiek innego. Świetny.
ALE, ALE, ALE, nie chcemy robić tego, co właśnie opisałem powyżej. Ponieważ pola publiczne są złe z wielu, wielu powodów. Jakie są nasze opcje? Jak opisuje John Skeet, oto nasze opcje:
Publiczna zmienna delegata (właśnie to zrobiliśmy powyżej. Nie rób tego. Właśnie powiedziałem powyżej, dlaczego jest źle)
Umieść delegata we właściwości za pomocą komendy get / set (problem polega na tym, że subskrybenci mogą się wzajemnie nadpisywać - więc możemy subskrybować kilka metod delegata, a następnie możemy przypadkowo powiedzieć PersonChangedDel = null, usuwając wszystkie pozostałe subskrypcje. innym problemem, który pozostaje tutaj, jest to, że ponieważ użytkownicy mają dostęp do delegata, mogą wywoływać cele na liście wywołań - nie chcemy, aby użytkownicy zewnętrzni mieli dostęp do momentu, w którym zgłaszają nasze zdarzenia.
Zmienna delegowana za pomocą metod AddXXXHandler i RemoveXXXHandler
Ta trzecia opcja jest zasadniczo tym, co daje nam wydarzenie. Kiedy deklarujemy EventHandler, daje nam dostęp do delegata - nie publicznie, nie jako właściwość, ale jako to, co nazywamy zdarzeniem, które właśnie dodało / usunęło akcesory.
Zobaczmy, jak wygląda ten sam program, ale teraz używam Eventu zamiast publicznego delegata (zmieniłem również naszego Mediatora na singleton):
Przykład 2: Z EventHandler zamiast z publicznego delegata
Mediator:
classMediator{privatestaticreadonlyMediator_Instance=newMediator();privateMediator(){}publicstaticMediatorGetInstance(){return_Instance;}publiceventEventHandler<PersonChangedEventArgs>PersonChanged;//this is just a property we expose to add items to the delegate.publicvoidOnPersonChanged(object sender,Person p){var personChangedDelegate =PersonChangedasEventHandler<PersonChangedEventArgs>;if(personChangedDelegate !=null){
personChangedDelegate(sender,newPersonChangedEventArgs(){Person= p });}}}
Zauważ, że jeśli użyjesz klawisza F12 w EventHandler, pokaże ci, że definicja jest tylko ogólnym delegatem z dodatkowym obiektem „nadawcy”:
Chociaż doceniam całą dobrą pracę w tym poście i lubię czytać większość z nich, nadal uważam, że jeden problem nie został rozwiązany - The other problem that remains here is that since the users have access to the delegate, they can invoke the targets in the invocation list -- we don't want external users having access to when to raise our events. W najnowszej wersji Mediatornadal możesz dzwonić za OnPersonChangekażdym razem, gdy masz odniesienie do singletonu. Może powinieneś wspomnieć, że takie Mediatorpodejście nie zapobiega temu konkretnemu zachowaniu i jest bliższe magistrali zdarzeń.
Ivaylo Slavov
6
Możesz także używać zdarzeń w deklaracjach interfejsu, a nie w przypadku delegatów.
Interfejs @surfen może zawierać zdarzenia, ale nie delegatów.
Alexandr Nikitin
1
Co dokładnie masz na myśli? Możesz mieć Action a { get; set; }wewnątrz definicji interfejsu.
Chiel ten Brinke
6
Co za wielkie nieporozumienie między wydarzeniami a delegatami !!! Delegat określa TYP (na przykład a classlub interfacenie), podczas gdy zdarzenie jest tylko rodzajem CZŁONKA (np. Pola, właściwości itp.). I, jak każdy inny członek, wydarzenie ma również swój typ. Jednak w przypadku zdarzenia typ zdarzenia musi zostać określony przez delegata. Na przykład NIE MOŻNA zadeklarować zdarzenia typu określonego przez interfejs.
Podsumowując, możemy dokonać następującej Obserwacji: rodzaj zdarzenia MUSI być zdefiniowany przez delegata . Jest to główna relacja między wydarzeniem a delegatem i została opisana w sekcji II.18 Definiowanie zdarzeń w partycjach od I do VI ECMA-335 (CLI) :
W typowym użyciu TypeSpec (jeśli jest obecny) identyfikuje delegata, którego podpis odpowiada argumentom przekazanym do metody wywoływania zdarzenia.
Jednak fakt ten NIE oznacza, że zdarzenie korzysta z pola delegowania kopii zapasowej . W rzeczywistości zdarzenie może wykorzystywać pole zaplecza dowolnego innego typu struktury danych, który wybierzesz. Jeśli zaimplementujesz zdarzenie jawnie w języku C #, możesz wybrać sposób przechowywania procedur obsługi zdarzeń (pamiętaj, że procedury obsługi zdarzeń są instancjami typu zdarzenia , które z kolei jest obowiązkowo typem delegata --- z poprzedniej obserwacji ). Można jednak przechowywać te procedury obsługi zdarzeń (które są instancjami delegowanymi) w strukturze danych, takiej jak a Listlub a Dictionarylub w dowolnym innym miejscu, a nawet w polu delegowania kopii zapasowej. Ale nie zapominaj, że NIE jest obowiązkowe korzystanie z pola delegowanego.
Zdarzenie w .net to wyznaczona kombinacja metody Add i Remove, które oczekują określonego rodzaju delegata. Zarówno C #, jak i vb.net mogą automatycznie generować kod dla metod dodawania i usuwania, które definiują uczestnika do przechowywania subskrypcji zdarzeń oraz dodawania / usuwania przekazanego w delegagte do / z tego uczestnika subskrypcji. VB.net automatycznie wygeneruje również kod (z instrukcją RaiseEvent), aby wywołać listę subskrypcji tylko wtedy, gdy nie jest pusta; z jakiegoś powodu C # nie generuje tego drugiego.
Należy pamiętać, że chociaż zarządzanie subskrypcjami zdarzeń przy użyciu delegata multiemisji jest powszechne, nie jest to jedyny sposób. Z publicznego punktu widzenia potencjalny subskrybent zdarzenia musi wiedzieć, jak powiadomić obiekt, że chce odbierać zdarzenia, ale nie musi wiedzieć, jakiego mechanizmu użyje wydawca, aby wywołać zdarzenia. Zauważ też, że chociaż ktokolwiek zdefiniował strukturę danych zdarzenia w .net najwyraźniej myślał, że powinien istnieć publiczny sposób ich podniesienia, ani C #, ani vb.net nie korzystają z tej funkcji.
Wydarzenie jest ODNIESIENIEM do delegata z dwoma ograniczeniami
Nie można wywoływać bezpośrednio
Nie można przypisać wartości bezpośrednio (np. EventObj = delegateMethod)
Powyżej dwóch są słabe punkty dla delegatów i jest to uwzględnione w przypadku. Kompletny przykładowy kod, aby pokazać różnicę w skrzypku jest tutaj https://dotnetfiddle.net/5iR3fB .
Przełącz komentarz między Zdarzeniem a Delegatem i kodem klienta, który wywołuje / przypisuje wartości do delegowania, aby zrozumieć różnicę
Oto kod wewnętrzny.
/*
This is working program in Visual Studio. It is not running in fiddler because of infinite loop in code.
This code demonstrates the difference between event and delegate
Event is an delegate reference with two restrictions for increased protection
1. Cannot be invoked directly
2. Cannot assign value to delegate reference directly
Toggle between Event vs Delegate in the code by commenting/un commenting the relevant lines
*/publicclassRoomTemperatureController{privateint _roomTemperature =25;//Default/Starting room Temperatureprivatebool _isAirConditionTurnedOn =false;//Default AC is Offprivatebool _isHeatTurnedOn =false;//Default Heat is Offprivatebool _tempSimulator =false;publicdelegatevoidOnRoomTemperatureChange(int roomTemperature);//OnRoomTemperatureChange is a type of Delegate (Check next line for proof)// public OnRoomTemperatureChange WhenRoomTemperatureChange;// { get; set; }//Exposing the delegate to outside world, cannot directly expose the delegate (line above), publiceventOnRoomTemperatureChangeWhenRoomTemperatureChange;// { get; set; }//Exposing the delegate to outside world, cannot directly expose the delegate (line above), publicRoomTemperatureController(){WhenRoomTemperatureChange+=InternalRoomTemperatuerHandler;}privatevoidInternalRoomTemperatuerHandler(int roomTemp){System.Console.WriteLine("Internal Room Temperature Handler - Mandatory to handle/ Should not be removed by external consumer of ths class: Note, if it is delegate this can be removed, if event cannot be removed");}//User cannot directly asign values to delegate (e.g. roomTempControllerObj.OnRoomTemperatureChange = delegateMethod (System will throw error)publicboolTurnRoomTeperatureSimulator{set{
_tempSimulator =value;if(value){SimulateRoomTemperature();//Turn on Simulator }}get{return _tempSimulator;}}publicvoidTurnAirCondition(bool val){
_isAirConditionTurnedOn = val;
_isHeatTurnedOn =!val;//Binary switch If Heat is ON - AC will turned off automatically (binary)System.Console.WriteLine("Aircondition :"+ _isAirConditionTurnedOn);System.Console.WriteLine("Heat :"+ _isHeatTurnedOn);}publicvoidTurnHeat(bool val){
_isHeatTurnedOn = val;
_isAirConditionTurnedOn =!val;//Binary switch If Heat is ON - AC will turned off automatically (binary)System.Console.WriteLine("Aircondition :"+ _isAirConditionTurnedOn);System.Console.WriteLine("Heat :"+ _isHeatTurnedOn);}publicasyncvoidSimulateRoomTemperature(){while(_tempSimulator){if(_isAirConditionTurnedOn)
_roomTemperature--;//Decrease Room Temperature if AC is turned Onif(_isHeatTurnedOn)
_roomTemperature++;//Decrease Room Temperature if AC is turned OnSystem.Console.WriteLine("Temperature :"+ _roomTemperature);if(WhenRoomTemperatureChange!=null)WhenRoomTemperatureChange(_roomTemperature);System.Threading.Thread.Sleep(500);//Every second Temperature changes based on AC/Heat Status}}}publicclassMySweetHome{RoomTemperatureController roomController =null;publicMySweetHome(){
roomController =newRoomTemperatureController();
roomController.WhenRoomTemperatureChange+=TurnHeatOrACBasedOnTemp;//roomController.WhenRoomTemperatureChange = null; //Setting NULL to delegate reference is possible where as for Event it is not possible.//roomController.WhenRoomTemperatureChange.DynamicInvoke();//Dynamic Invoke is possible for Delgate and not possible with Event
roomController.SimulateRoomTemperature();System.Threading.Thread.Sleep(5000);
roomController.TurnAirCondition(true);
roomController.TurnRoomTeperatureSimulator=true;}publicvoidTurnHeatOrACBasedOnTemp(int temp){if(temp >=30)
roomController.TurnAirCondition(true);if(temp <=15)
roomController.TurnHeat(true);}publicstaticvoidMain(string[]args){MySweetHome home =newMySweetHome();}}
Jeśli zaznaczysz język średniozaawansowany, będziesz wiedział, że kompilator .net konwertuje delegata na zapieczętowaną klasę w IL z niektórymi funkcjami wbudowanymi, takimi jak invoke, beginInvoke, endInvoke i klasa delegowana odziedziczona z innej klasy, być może nazywanej „SystemMulticast”. Wydaje mi się, że Event jest klasą potomną Delegata z pewnymi dodatkowymi właściwościami.
Różnica między instancją zdarzenia a delegatem polega na tym, że nie można uruchomić zdarzenia poza deklaracją. Jeśli zadeklarujesz zdarzenie w klasie A, możesz je uruchomić tylko w klasie A. Jeśli zadeklarujesz uczestnika w klasie A, możesz go użyć w dowolnym miejscu. Myślę, że to główna różnica między nimi
Odpowiedzi:
Event deklaracja dodatkowa warstwa abstrakcji i ochrony na delegata instancji. Ta ochrona zapobiega resetowaniu klientów delegata i jego listy wywołań przez klientów delegata i umożliwia jedynie dodawanie lub usuwanie celów z listy wywołań.
źródło
Aby zrozumieć różnice, spójrz na 2 przykłady
Przykład z delegatami (w tym przypadku akcja - to rodzaj delegata, który nie zwraca wartości)
Aby użyć delegata, powinieneś zrobić coś takiego:
Ten kod działa dobrze, ale możesz mieć słabe punkty.
Na przykład, jeśli napiszę to:
z ostatnim wierszem kodu zastąpiłem poprzednie zachowania tylko jednym brakującym
+
(użyłem=
zamiast+=
)Innym słabym punktem jest to, że każda klasa, która korzysta z twojej
Animal
klasy, może podbićRaiseEvent
tylko ją sprawdzającanimal.RaiseEvent()
.Aby uniknąć tych słabych punktów, możesz użyć
events
w c #.Twoja klasa zwierząt zmieni się w ten sposób:
wywoływać wydarzenia
Różnice:
Uwagi:
EventHandler jest zadeklarowany jako następujący delegat:
bierze nadawcę (typu Object) i argumenty zdarzenia. Nadawca ma wartość zerową, jeśli pochodzi z metod statycznych.
Ten przykład, który wykorzystuje
EventHandler<ArgsSpecial>
, można również napisać za pomocąEventHandler
.Patrz tutaj dla dokumentacji o EventHandler
źródło
RaiseEvent
dopóki metoda wywołująca ma dostęp do wystąpieniaanimal
w kodzie używającym zdarzenia?animal.Run(this, new ArgsSpecial("Run faster");
?Oprócz właściwości składniowych i operacyjnych istnieje również różnica semantyczna.
Delegaci są koncepcyjnie szablonami funkcji; oznacza to, że wyrażają umowę, do której musi się stosować funkcja, aby można było uznać ją za „typ” delegata.
Wydarzenia reprezentują ... cóż, wydarzenia. Mają na celu ostrzec kogoś, gdy coś się wydarzy i tak, przestrzegają definicji delegata, ale to nie to samo.
Nawet jeśli byłyby dokładnie takie same (składniowe i w kodzie IL), nadal pozostanie różnica semantyczna. Ogólnie wolę mieć dwie różne nazwy dla dwóch różnych pojęć, nawet jeśli są one implementowane w ten sam sposób (co nie znaczy, że lubię mieć ten sam kod dwa razy).
źródło
Oto kolejny dobry link do odniesienia. http://csharpindepth.com/Articles/Chapter2/Events.aspx
W skrócie, odejmij od artykułu - Wydarzenia są enkapsulacją nad delegatami.
Cytat z artykułu:
źródło
public Delegate
zmienna będzie wystawienie „dane”, ale do mojej najlepszej OOP wiedzy nigdy wspomniane koncepcje zupełnie jakDelegate
(jest to ani „obiektem”, ani „komunikat”) , a .NET tak naprawdę ledwo traktuje delegatów jak dane.AddXXXHandler
metod za pomocąprivate Delegate
zmiennej. W takim przypadku możesz sprawdzić, czy moduł obsługi jest już ustawiony i odpowiednio zareagować. Może to być również dobre ustawienie, jeśli potrzebujesz obiektu trzymającego,Delegate
aby móc wyczyścić wszystkie programy obsługi (event
nie daje ci to żadnej możliwości).UWAGA: Jeśli masz dostęp do wersji C # 5.0 Unleashed , przeczytaj „Ograniczenia w zwykłym korzystaniu z usług delegatów” w rozdziale 18, zatytułowanym „Wydarzenia”, aby lepiej zrozumieć różnice między nimi.
Zawsze pomaga mi mieć prosty, konkretny przykład. Oto jeden dla społeczności. Najpierw pokażę, w jaki sposób możesz wykorzystać delegatów samodzielnie, aby zrobić to, co dla nas robią Wydarzenia. Następnie pokazuję, jak to samo rozwiązanie działałoby z instancją
EventHandler
. A potem wyjaśniam, dlaczego NIE chcemy robić tego, co wyjaśnię w pierwszym przykładzie. Ten post został zainspirowany artykułem Johna Skeeta.Przykład 1: Korzystanie z publicznego delegata
Załóżmy, że mam aplikację WinForms z jednym rozwijanym polem. Lista rozwijana jest powiązana z
List<Person>
. Gdzie osoba ma właściwości Id, Name, NickName, HairColor. W formularzu głównym znajduje się niestandardowy element sterujący użytkownika, który pokazuje właściwości tej osoby. Gdy ktoś wybierze osobę z listy rozwijanej, etykiety w aktualizacji kontroli użytkownika pokazują właściwości wybranej osoby.Oto jak to działa. Mamy trzy pliki, które pomagają nam to połączyć:
Oto odpowiedni kod dla każdej z klas:
Oto nasza kontrola użytkownika:
Wreszcie mamy następujący kod w naszym Form1.cs. Tutaj nazywamy się OnPersonChanged, który wywołuje dowolny kod subskrybowany uczestnikowi.
Ok. W ten sposób działałoby to bez użycia wydarzeń i po prostu przy użyciu delegatów . Po prostu umieszczamy delegata publicznego na zajęciach - możesz ustawić go jako statyczny, singleton, czy cokolwiek innego. Świetny.
ALE, ALE, ALE, nie chcemy robić tego, co właśnie opisałem powyżej. Ponieważ pola publiczne są złe z wielu, wielu powodów. Jakie są nasze opcje? Jak opisuje John Skeet, oto nasze opcje:
PersonChangedDel = null
, usuwając wszystkie pozostałe subskrypcje. innym problemem, który pozostaje tutaj, jest to, że ponieważ użytkownicy mają dostęp do delegata, mogą wywoływać cele na liście wywołań - nie chcemy, aby użytkownicy zewnętrzni mieli dostęp do momentu, w którym zgłaszają nasze zdarzenia.Ta trzecia opcja jest zasadniczo tym, co daje nam wydarzenie. Kiedy deklarujemy EventHandler, daje nam dostęp do delegata - nie publicznie, nie jako właściwość, ale jako to, co nazywamy zdarzeniem, które właśnie dodało / usunęło akcesory.
Zobaczmy, jak wygląda ten sam program, ale teraz używam Eventu zamiast publicznego delegata (zmieniłem również naszego Mediatora na singleton):
Przykład 2: Z EventHandler zamiast z publicznego delegata
Mediator:
Zauważ, że jeśli użyjesz klawisza F12 w EventHandler, pokaże ci, że definicja jest tylko ogólnym delegatem z dodatkowym obiektem „nadawcy”:
Kontrola użytkownika:
Wreszcie, oto kod Form1.cs:
Ponieważ EventHandler chce i EventArgs jako parametr, stworzyłem tę klasę z jedną tylko właściwością:
Mam nadzieję, że to trochę pokazuje, dlaczego mamy wydarzenia i jak różnią się one - ale funkcjonalnie takie same - jak delegaci.
źródło
The other problem that remains here is that since the users have access to the delegate, they can invoke the targets in the invocation list -- we don't want external users having access to when to raise our events
. W najnowszej wersjiMediator
nadal możesz dzwonić zaOnPersonChange
każdym razem, gdy masz odniesienie do singletonu. Może powinieneś wspomnieć, że takieMediator
podejście nie zapobiega temu konkretnemu zachowaniu i jest bliższe magistrali zdarzeń.Możesz także używać zdarzeń w deklaracjach interfejsu, a nie w przypadku delegatów.
źródło
Action a { get; set; }
wewnątrz definicji interfejsu.Co za wielkie nieporozumienie między wydarzeniami a delegatami !!! Delegat określa TYP (na przykład a
class
lubinterface
nie), podczas gdy zdarzenie jest tylko rodzajem CZŁONKA (np. Pola, właściwości itp.). I, jak każdy inny członek, wydarzenie ma również swój typ. Jednak w przypadku zdarzenia typ zdarzenia musi zostać określony przez delegata. Na przykład NIE MOŻNA zadeklarować zdarzenia typu określonego przez interfejs.Podsumowując, możemy dokonać następującej Obserwacji: rodzaj zdarzenia MUSI być zdefiniowany przez delegata . Jest to główna relacja między wydarzeniem a delegatem i została opisana w sekcji II.18 Definiowanie zdarzeń w partycjach od I do VI ECMA-335 (CLI) :
Jednak fakt ten NIE oznacza, że zdarzenie korzysta z pola delegowania kopii zapasowej . W rzeczywistości zdarzenie może wykorzystywać pole zaplecza dowolnego innego typu struktury danych, który wybierzesz. Jeśli zaimplementujesz zdarzenie jawnie w języku C #, możesz wybrać sposób przechowywania procedur obsługi zdarzeń (pamiętaj, że procedury obsługi zdarzeń są instancjami typu zdarzenia , które z kolei jest obowiązkowo typem delegata --- z poprzedniej obserwacji ). Można jednak przechowywać te procedury obsługi zdarzeń (które są instancjami delegowanymi) w strukturze danych, takiej jak a
List
lub aDictionary
lub w dowolnym innym miejscu, a nawet w polu delegowania kopii zapasowej. Ale nie zapominaj, że NIE jest obowiązkowe korzystanie z pola delegowanego.źródło
Zdarzenie w .net to wyznaczona kombinacja metody Add i Remove, które oczekują określonego rodzaju delegata. Zarówno C #, jak i vb.net mogą automatycznie generować kod dla metod dodawania i usuwania, które definiują uczestnika do przechowywania subskrypcji zdarzeń oraz dodawania / usuwania przekazanego w delegagte do / z tego uczestnika subskrypcji. VB.net automatycznie wygeneruje również kod (z instrukcją RaiseEvent), aby wywołać listę subskrypcji tylko wtedy, gdy nie jest pusta; z jakiegoś powodu C # nie generuje tego drugiego.
Należy pamiętać, że chociaż zarządzanie subskrypcjami zdarzeń przy użyciu delegata multiemisji jest powszechne, nie jest to jedyny sposób. Z publicznego punktu widzenia potencjalny subskrybent zdarzenia musi wiedzieć, jak powiadomić obiekt, że chce odbierać zdarzenia, ale nie musi wiedzieć, jakiego mechanizmu użyje wydawca, aby wywołać zdarzenia. Zauważ też, że chociaż ktokolwiek zdefiniował strukturę danych zdarzenia w .net najwyraźniej myślał, że powinien istnieć publiczny sposób ich podniesienia, ani C #, ani vb.net nie korzystają z tej funkcji.
źródło
Aby w prosty sposób zdefiniować zdarzenie:
Wydarzenie jest ODNIESIENIEM do delegata z dwoma ograniczeniami
Powyżej dwóch są słabe punkty dla delegatów i jest to uwzględnione w przypadku. Kompletny przykładowy kod, aby pokazać różnicę w skrzypku jest tutaj https://dotnetfiddle.net/5iR3fB .
Przełącz komentarz między Zdarzeniem a Delegatem i kodem klienta, który wywołuje / przypisuje wartości do delegowania, aby zrozumieć różnicę
Oto kod wewnętrzny.
źródło
Delegat jest wskaźnikiem funkcji bezpiecznym dla typu. Event jest implementacją wzorca projektowego wydawca-subskrybent przy użyciu delegata.
źródło
Jeśli zaznaczysz język średniozaawansowany, będziesz wiedział, że kompilator .net konwertuje delegata na zapieczętowaną klasę w IL z niektórymi funkcjami wbudowanymi, takimi jak invoke, beginInvoke, endInvoke i klasa delegowana odziedziczona z innej klasy, być może nazywanej „SystemMulticast”. Wydaje mi się, że Event jest klasą potomną Delegata z pewnymi dodatkowymi właściwościami.
Różnica między instancją zdarzenia a delegatem polega na tym, że nie można uruchomić zdarzenia poza deklaracją. Jeśli zadeklarujesz zdarzenie w klasie A, możesz je uruchomić tylko w klasie A. Jeśli zadeklarujesz uczestnika w klasie A, możesz go użyć w dowolnym miejscu. Myślę, że to główna różnica między nimi
źródło