Jakie są różnice między delegatami a wydarzeniami?

Jakie są różnice między delegatami a wydarzeniami? Czy oba nie zawierają odwołań do funkcji, które mogą być wykonywane?

Author: 5StringRyan, 2008-08-27

10 answers

An Eventdeclaration dodaje warstwę abstrakcji i ochrony na instancji delegate. Ta ochrona uniemożliwia klientom delegata Resetowanie delegata i jego listy wywoływania i pozwala tylko na dodawanie lub usuwanie obiektów docelowych z listy wywoływania.

 249
Author: mmcdole,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-26 23:16:18

Oprócz właściwości składniowych i operacyjnych, istnieje również różnica semantyczna.

Delegaci są, koncepcyjnie, szablonami funkcji; to znaczy wyrażają umowę, której musi przestrzegać funkcja, aby została uznana za" typ " delegata.

Zdarzenia reprezentują ... wydarzenia. Mają za zadanie ostrzec kogoś, gdy coś się dzieje i tak, stosują się do definicji delegata, ale nie są tym samym.

Nawet gdyby były dokładnie to samo (składniowo i w kodzie IL) nadal pozostanie różnica semantyczna. Generalnie wolę mieć dwie różne nazwy dla dwóch różnych pojęć, nawet jeśli są one zaimplementowane w ten sam sposób (co nie znaczy, że lubię mieć ten sam kod dwa razy).

 90
Author: Jorge Córdoba,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-26 20:14:56

Aby zrozumieć różnice można spojrzeć na ten 2 Przykłady

Przykład z delegatami (w tym przypadku akcja - czyli rodzaj 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:

Animal 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ć pewne 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") ;

W ostatniej linijce kodu, nadpisałem poprzednie zachowania tylko z jednym brakującym + (użyłem = zamiast +=)

Kolejnym słabym punktem jest to, że każda klasa, która używa twojej klasy Animal może podnieść RaiseEvent po prostu ją nazywając animal.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:

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

    public string Operation {get; set;}
} 

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

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

Aby wywołać zdarzenia

 Animal 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 publicznego pola (używając zdarzeń, kompilator chroni Twoje pola przed niechcianymi dostęp)
  2. zdarzenia nie mogą być przypisane bezpośrednio. W tym przypadku nie spowoduje to wcześniejszego błędu, który pokazałem z nadpisaniem zachowania.
  3. Nikt poza Twoją klasą nie może podnieść imprezy.
  4. zdarzenia mogą być zawarte w deklaracji interfejsu, podczas gdy Pole nie może

Uwagi:

EventHandler jest zadeklarowany jako następujący delegat:

public delegate void EventHandler (object sender, EventArgs e)

Pobiera nadawcę (typu Object) i argumenty event. Nadawca jest null, jeżeli pochodzi z metod statycznych.

Ten przykład, który używa EventHandler<ArgsSpecial>, może być również napisany za pomocą EventHandler.

Zobacz tutaj {[57] } aby uzyskać dokumentację o EventHandler

 80
Author: faby,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-19 10:44:08

Jest to stary post, ale jeśli ktoś się na niego natknie, tak jak ja-oto kolejny dobry link do którego można się odnieść.. http://csharpindepth.com/Articles/Chapter2/Events.aspx

Krótko mówiąc, wyjęcie z artykułu-wydarzenia są hermetyzowane przez delegatów. Cytat z Artykułu -

"przypuśćmy, że zdarzenia nie istnieją jako pojęcie w C#/. NET. jak kolejna klasa zapisuje się na wydarzenie?

Trzy opcje:

  1. Publiczne delegatevariable

  2. Delegate zmienna wspierana przez właściwość

  3. Deleguj zmienną za pomocą metod AddXXXHandler i RemoveXXXHandler

Opcja 1 jest ewidentnie okropna, ze wszystkich normalnych powodów brzydzimy się zmienne publiczne.

Opcja 2 jest lepsza, ale pozwala subskrybentom skutecznie nadpisać siebie nawzajem - byłoby zbyt łatwo napisać somewstance.Plotdata = eventHandler; które zastąpiłyby wszelkie istniejące zdarzenia handlowcy raczej niż dodanie nowego. Dodatkowo trzeba jeszcze napisać właściwości.

Opcja 3 jest w zasadzie tym, co dają Ci wydarzenia, ale z gwarantowaną konwencja (generowana przez kompilator i wspierana przez dodatkowe flagi w IL) i" darmową " implementację jeśli jesteś zadowolony z semantyki takie wydarzenia w terenie dają. Subskrypcja i rezygnacja z subskrypcji zdarzenia są zamykane bez umożliwienia arbitralnego dostępu do listy obsługi zdarzeń i języków może uprościć sprawy, dostarczając składnia zarówno deklaracji, jak i subskrypcji."

 35
Author: vibhu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-12 19:09:08

Można również używać zdarzeń w deklaracjach interfejsu, nie dla delegatów.

 6
Author: Paul Hill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-25 14:37:21

UWAGA: Jeśli masz dostęp do C # 5.0 Unleashed, przeczytaj "ograniczenia zwykłego korzystania z delegatów" w rozdziale 18 zatytułowanym "zdarzenia", aby lepiej zrozumieć różnice między nimi.


Zawsze pomaga mi mieć prosty, konkretny przykład. Oto jeden dla społeczności. Najpierw pokażę, jak możesz używać samych delegatów, aby robić to, co robią dla nas wydarzenia. Następnie pokazuję, jak to samo rozwiązanie będzie działać z instancją EventHandler. A potem wyjaśnię, dlaczego nie chcemy robić co wyjaśniam w pierwszym przykładzie. Ten post został zainspirowany artykułem autorstwa Johna Skeeta.

Przykład 1: Użycie delegata publicznego

Załóżmy, że mam aplikację WinForms z pojedynczym rozwijanym polem. Lista rozwijana jest związana z List<Person>. Gdzie osoba ma właściwości Id, Imię, Nick, kolor włosów. W głównym formularzu znajduje się niestandardowa kontrola użytkownika, która pokazuje właściwości tej osoby. Gdy ktoś wybiera osobę z listy rozwijanej, etykiety w aktualizacji kontrola użytkownika pokazują właściwości wybranej osoby.

Tutaj wpisz opis obrazka

Oto Jak to działa. Mamy trzy pliki, które pomagają nam to połączyć:

    Mediator.cs -- static class przechowuje delegatów
  • Form1.cs -- main form
  • DetailView.cs -- user control pokazuje wszystkie szczegóły

Oto odpowiedni kod dla każdej z klas:

class Mediator
{
    public delegate void PersonChangedDelegate(Person p); //delegate type definition
    public static PersonChangedDelegate PersonChangedDel; //delegate instance. Detail view will "subscribe" to this.
    public static void OnPersonChanged(Person p) //Form1 will call this when the drop-down changes.
    {
        if (PersonChangedDel != null)
        {
            PersonChangedDel(p);
        }
    }
}

Oto nasza kontrola użytkownika:

public partial class DetailView : UserControl
{
    public DetailView()
    {
        InitializeComponent();
        Mediator.PersonChangedDel += DetailView_PersonChanged;
    }

    void DetailView_PersonChanged(Person p)
    {
        BindData(p);
    }

    public void BindData(Person p)
    {
        lblPersonHairColor.Text = p.HairColor;
        lblPersonId.Text = p.IdPerson.ToString();
        lblPersonName.Text = p.Name;
        lblPersonNickName.Text = p.NickName;

    }
}

Wreszcie mamy następujący kod w naszym Formula1.cs. Tutaj wywołujemy OnPersonChanged, który wywołuje dowolny kod subskrybowany przez delegata.

private void 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. Więc tak to działa bez użycia zdarzeń i po prostu używając delegatów . Umieściliśmy delegata publicznego na zajęciach. możesz zrobić statyczny, singleton, czy cokolwiek. Świetnie.

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 mamy opcje? Jako John Skeet opisuje, oto nasze opcje:

  1. zmienna publicznego delegata (to właśnie zrobiliśmy powyżej. nie rób tego. po prostu powiedziałem ci powyżej, dlaczego jest źle)
  2. umieść delegata w właściwości z get / set (problem polega na tym, że subskrybenci mogą się nadpisywać - więc możemy subskrybować kilka metod do delegata, a następnie przypadkowo powiedzieć PersonChangedDel = null, wymazując wszystkie inne subskrypcje. Innym problemem, który pozostaje tutaj jest to, że od użytkownicy mają dostęp do delegata, mogą wywoływać cele z listy zaproszeń - nie chcemy, aby zewnętrzni użytkownicy mieli dostęp do kiedy zgłaszać nasze zdarzenia.
  3. zmienna delegata z metodami AddXXXHandler i RemoveXXXHandler

Ta trzecia opcja jest zasadniczo tym, co daje nam wydarzenie. Kiedy deklarujemy EventHandler, daje nam to dostęp do delegata - nie publicznie , nie jako właściwość, ale jako tę rzecz nazywamy zdarzenie, które właśnie dodało/usunęło accessors.

Zobaczmy, jak wygląda ten sam program, ale teraz używając zdarzenia zamiast delegata publicznego (zmieniłem również naszego mediatora na singleton):

Przykład 2: z EventHandler zamiast delegata publicznego

Mediator:

class Mediator
{

    private static readonly Mediator _Instance = new Mediator();

    private Mediator() { }

    public static Mediator GetInstance()
    {
        return _Instance;
    }

    public event EventHandler<PersonChangedEventArgs> PersonChanged; //this is just a property we expose to add items to the delegate.

    public void OnPersonChanged(object sender, Person p)
    {
        var personChangedDelegate = PersonChanged as EventHandler<PersonChangedEventArgs>;
        if (personChangedDelegate != null)
        {
            personChangedDelegate(sender, new PersonChangedEventArgs() { Person = p });
        }
    }
}

Zauważ, że jeśli F12 na EventHandler, pokaże ci, że definicja jest tylko generycznym-ified delegatem z dodatkowym obiektem "sender":

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

Kontrola Użytkownika:

public partial class DetailView : UserControl
{
    public DetailView()
    {
        InitializeComponent();
        Mediator.GetInstance().PersonChanged += DetailView_PersonChanged;
    }

    void DetailView_PersonChanged(object sender, PersonChangedEventArgs e)
    {
        BindData(e.Person);
    }

    public void BindData(Person p)
    {
        lblPersonHairColor.Text = p.HairColor;
        lblPersonId.Text = p.IdPerson.ToString();
        lblPersonName.Text = p.Name;
        lblPersonNickName.Text = p.NickName;

    }
}

Wreszcie, Oto Formula1.kod cs:

private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
        Mediator.GetInstance().OnPersonChanged(this, (Person)comboBox1.SelectedItem);
}

Ponieważ EventHandler chce i EventArgs jako parametr, utworzyłem tę klasę z tylko jedną właściwością:

class PersonChangedEventArgs
{
    public Person Person { get; set; }
}

Miejmy nadzieję, że to pokaże Ci trochę o tym, dlaczego mamy wydarzenia i jak są różne - ale funkcjonalnie takie same-jak delegaci.

 6
Author: Trevor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:33:24

Co za wielkie nieporozumienie między wydarzeniami a delegatami!!! Delegat określa typ (np. class LUB interface does), podczas gdy zdarzenie jest tylko rodzajem elementu składowego (np. pola, właściwości itp.). I, podobnie jak każdy inny rodzaj członka, wydarzenie również ma swój typ. Jednak w przypadku zdarzenia, typ zdarzenia musi być określony przez delegata. Na przykład, nie Można zadeklarować Zdarzenia typu zdefiniowanego przez interfejs.

Podsumowując, możemy wykonać następujące obserwacja: typ zdarzenia musi być zdefiniowany przez delegata . Jest to główna relacja między zdarzeniem a delegatem i jest opisana w sekcji II. 18 Definiowanie zdarzeń z partycji ECMA-335 (CLI) od I do VI :

W typowym użyciu TypeSpec (jeśli występuje) identyfikuje delegata , którego sygnatura pasuje do argumentów przekazywanych do metody ognia zdarzenia.

Jednakże ten fakt nie oznacza, że zdarzenie używa backing delegate field . W rzeczywistości zdarzenie może wykorzystywać pole zapasowe dowolnego wybranego typu struktury danych. Jeśli zaimplementujesz Zdarzenie jawnie w C#, możesz wybrać sposób przechowywania procedury obsługi zdarzeń (zauważ, że procedury obsługi zdarzeń są instancjami Typu Zdarzenia , które z kolei jest obowiązkowo typem delegata - - - z poprzedniej obserwacji ). Możesz jednak przechowywać te procedury obsługi zdarzeń (które są instancjami delegowanymi) w struktura danych, taka jak List lub Dictionary lub dowolna inna, lub nawet w polu delegata wspierającego. Ale nie zapominaj, że korzystanie z pola delegata nie jest obowiązkowe.

 6
Author: Miguel Gamboa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-01 09:43:03

Zdarzenie w. Net jest wyznaczoną kombinacją metody Add i metody Remove, które oczekują określonego typu delegata. Zarówno C# jak i vb.net może automatycznie wygenerować kod dla metod dodawania i usuwania, które zdefiniują delegata do przechowywania subskrypcji zdarzeń, i dodać / usunąć przekazany w delegagte do / z tego delegata subskrypcji. VB.net będzie również automatycznie generować kod (z instrukcją RaiseEvent), aby wywołać listę subskrypcji wtedy i tylko wtedy, gdy nie jest pusta; dla niektórych powód, C# nie generuje tego drugiego.

Zauważ, że chociaż powszechne jest zarządzanie subskrypcjami zdarzeń za pomocą delegata multicastu,nie jest to jedyny sposób na to. Z publicznego punktu widzenia, potencjalny subskrybent zdarzeń musi wiedzieć, jak informować obiekt, że chce otrzymywać zdarzenia, ale nie musi wiedzieć, jakiego mechanizmu wydawca użyje do zgłaszania zdarzeń. Zauważ również, że chociaż ktokolwiek zdefiniował strukturę danych zdarzeń w. Net, najwyraźniej uważał, że powinno być publicznych sposobów ich wychowywania, ani C#, ani vb.net korzysta z tej funkcji.

 4
Author: supercat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-27 17:07:07

Aby zdefiniować o zdarzeniu w prosty sposób:

Zdarzenie jest odniesieniem do delegata z dwoma ograniczeniami

  1. nie można wywoływać bezpośrednio
  2. nie można bezpośrednio przypisać wartości (np. eventObj = delegateMethod)

Powyżej dwa są słabymi punktami dla delegatów i jest on adresowany w przypadku. Pełna próbka kodu, aby pokazać różnicę w fiddler jest tutaj https://dotnetfiddle.net/5iR3fB .

Przełącz komentarz między Kod zdarzenia i delegata oraz klienta, który wywołuje / przypisuje wartości do delegata, aby zrozumieć różnicę

Oto kod inline.

 /*
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
*/

public class RoomTemperatureController
{
    private int _roomTemperature = 25;//Default/Starting room Temperature
    private bool _isAirConditionTurnedOn = false;//Default AC is Off
    private bool _isHeatTurnedOn = false;//Default Heat is Off
    private bool _tempSimulator = false;
    public  delegate void OnRoomTemperatureChange(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), 
    public  event OnRoomTemperatureChange WhenRoomTemperatureChange;// { get; set; }//Exposing the delegate to outside world, cannot directly expose the delegate (line above), 

    public RoomTemperatureController()
    {
        WhenRoomTemperatureChange += InternalRoomTemperatuerHandler;
    }
    private void InternalRoomTemperatuerHandler(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)
    public bool TurnRoomTeperatureSimulator
    {
        set
        {
            _tempSimulator = value;
            if (value)
            {
                SimulateRoomTemperature(); //Turn on Simulator              
            }
        }
        get { return _tempSimulator; }
    }
    public void TurnAirCondition(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);

    }
    public void TurnHeat(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);

    }

    public async void SimulateRoomTemperature()
    {
        while (_tempSimulator)
        {
            if (_isAirConditionTurnedOn)
                _roomTemperature--;//Decrease Room Temperature if AC is turned On
            if (_isHeatTurnedOn)
                _roomTemperature++;//Decrease Room Temperature if AC is turned On
            System.Console.WriteLine("Temperature :" + _roomTemperature);
            if (WhenRoomTemperatureChange != null)
                WhenRoomTemperatureChange(_roomTemperature);
            System.Threading.Thread.Sleep(500);//Every second Temperature changes based on AC/Heat Status
        }
    }

}

public class MySweetHome
{
    RoomTemperatureController roomController = null;
    public MySweetHome()
    {
        roomController = new RoomTemperatureController();
        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;

    }
    public void TurnHeatOrACBasedOnTemp(int temp)
    {
        if (temp >= 30)
            roomController.TurnAirCondition(true);
        if (temp <= 15)
            roomController.TurnHeat(true);

    }
    public static void Main(string []args)
    {
        MySweetHome home = new MySweetHome();
    }


}
 3
Author: Venkatesh Muniyandi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-13 00:09:39

Covariance i Contravariance zapewniają dodatkową elastyczność obiektom delegatów. Z drugiej strony, wydarzenie nie ma takich pojęć.

  • Covariance pozwala przypisać metodę do delegata, gdzie zwracanym typem metody jest klasa, która pochodzi z klasy określa typ zwracanego delegata.
  • Contravariance umożliwia przypisanie metody do delegata, gdzie parametr Typ metody jest klasą bazową klasy, która jest określony jako parametr delegat.
 0
Author: vivek nuna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-29 06:35:59