В чем разница между событиями и делегатами действий [duplicate]

Другой способ сделать это, если у вас есть тонна коммитов, - сделать сквош ПОСЛЕ фиксации, как git rebase -i

. Это откроет ваш редактор, чтобы выбрать / сквош, как обычно.

См. https://git-scm.com/docs/git-rebase#_interactive_mode

270
задан 5StringRyan 30 September 2011 в 16:09
поделиться

10 ответов

Объявление события добавляет слой абстракции и защиты экземпляра делегата. Эта защита предотвращает сброс делегата делегата и его списка вызовов и позволяет добавлять или удалять цели из списка вызовов.

249
ответ дан mmcdole 31 August 2018 в 16:38
поделиться

ПРИМЕЧАНИЕ. Если у вас есть доступ к C # 5.0 Unleashed , прочитайте «Ограничения на обычное использование делегатов» в главе 18 под названием «События», чтобы лучше понять различия между ними.


Это всегда помогает мне составить простой, конкретный пример. Итак, вот для сообщества. Сначала я покажу, как вы можете использовать только делегатов, чтобы делать то, что делают для нас события. Затем я покажу, как одно и то же решение будет работать с экземпляром EventHandler. И затем я объясню, почему мы НЕ хотим делать то, что я объясняю в первом примере.

Пример 1: Использование public delegate

Предположим, у меня есть приложение WinForms с одним раскрывающимся списком коробка. Выпадающий объект привязан к List<Person>. Где Person имеет свойства Id, Name, NickName, HairColor. В основной форме используется пользовательский элемент управления, который показывает свойства этого человека. Когда кто-то выбирает человека в раскрывающемся списке меток в обновлении пользовательского управления, чтобы показать свойства выбранного человека.

enter image description here [/g5]

Вот как это работает. У нас есть три файла, которые помогают нам объединить это:

  • Mediator.cs - статический класс содержит делегатов
  • Form1.cs - основная форма
  • DetailView.cs - пользовательский элемент управления показывает все подробности

Вот соответствующий код для каждого из классов:

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);
        }
    }
}

Вот наш пользовательский элемент управления:

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;

    }
}

Наконец, у нас есть следующий код в нашем Form1.cs. Здесь мы вызываем OnPersonChanged, который вызывает любой код, подписанный на делегат.

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`.
}

Хорошо. Таким образом, вы получите эту работу , не используя события и , просто используя делегаты . Мы просто помещаем публичный делегат в класс - вы можете сделать его статическим или одноэлементным или любым другим. Отлично.

НО, НО, НО, мы не хотим делать то, что я только что описал выше. Потому что публичные поля плохие по многим причинам. Итак, каковы наши варианты? Как описывает Джон Скит, вот наши варианты:

  1. Публичная переменная делегата (это то, что мы только что сделали выше, не делайте этого, я просто сказал вам выше, почему это плохо)
  2. Поместите делегат в свойство с get / set (проблема заключается в том, что подписчики могут переопределять друг друга, поэтому мы можем подписывать кучу методов делегату, а затем мы могли бы случайно сказать PersonChangedDel = null, вытирая Другие проблемы, которые остаются здесь, это то, что, поскольку пользователи имеют доступ к делегату, они могут вызывать цели в списке вызовов - мы не хотим, чтобы внешние пользователи имели доступ к тому, когда нужно поднимать наши события .
  3. Переменная делегата с методами AddXXXHandler и RemoveXXXHandler

Эта третья опция - это то, что нам дает событие. Когда мы объявляем EventHandler, он дает нам доступ к делегат - не публично, а не как свойство, но в качестве этой вещи мы называем событие, которое просто добавляет / удаляет аксессоры.

Давайте посмотрим то, что похоже на ту же программу, но теперь использует событие вместо публичного делегата (я также изменил наш посредник на одноэлементный):

Пример 2: с EventHandler вместо публичного делегата

Посредник:

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 });
        }
    }
}

Обратите внимание, что если вы F12 в EventHandler, он покажет вам, что это определение является только делегатом с общим назначением с дополнительным объектом «отправитель»:

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

Пользовательский контроль:

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;

    }
}

Наконец, вот код Form1.cs:

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

Поскольку EventHandler хочет и EventArgs в качестве параметра, я создал этот класс только с одним свойством:

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

Надеюсь, это покажет вам немного о том, почему у нас есть события и как они разные, но функционально одинаковы - как делегаты.

6
ответ дан Community 31 August 2018 в 16:38
поделиться

Это старый пост, но если кто-то наткнулся на него, как и я, - вот еще одна хорошая ссылка для ссылки .. http://csharpindepth.com/Articles/Chapter2/Events.aspx

вкратце, отвлечься от статьи. События - это инкапсуляция над делегатами. Цитата из статьи -

«Предположим, что события не существовали как концепция в C # /. NET. Как другой класс подписался бы на событие?

Три варианта:

  1. public delegatevariable
  2. переменная делегата, поддерживаемая свойством
  3. делегата с помощью методов AddXXXHandler и RemoveXXXHandler

Опция 1 явно ужасен, по всем обычным причинам мы ненавидим публичные переменные.

Вариант 2 лучше, но позволяет подписчикам эффективно переопределять друг друга - было бы слишком легко написать someInstance.MyEvent = eventHandler; который заменит любые существующие обработчики событий, а не добавит новый. Кроме того, вам все равно нужно написать свойства.

Вариант 3 - это в основном то, что события дают вам, но с гарантированным соглашением (сгенерированным компилятор и подкрепляется дополнительными флагами в IL) и «бесплатной» реализацией, если вы довольны семантикой, которую дают вам события, подобные полям. Подписывание и отказ от подписки ng из событий инкапсулируется без разрешения произвольного доступа к списку обработчиков событий, а языки упрощают работу, предоставляя синтаксис как декларации, так и подписки ».

35
ответ дан Ian R. O'Brien 31 August 2018 в 16:38
поделиться

Какое большое недоразумение между событиями и делегатами !!! Делегат задает тип TYPE (например, class или interface), тогда как событие является просто своего рода членом (например, полями, свойствами и т. Д.). И, как и любой другой член, событие имеет тип. Тем не менее, в случае события тип события должен быть указан делегатом. Например, вы НЕ МОЖЕТ объявлять событие типа, определенного интерфейсом.

В заключение мы можем сделать следующее наблюдение: тип события ДОЛЖЕН быть определен делегатом. Это основное отношение между событием и делегатом и описано в разделе II.18 Определение событий разделов I-VI ECMA-335 (CLI): :

При типичном использовании TypeSpec (если присутствует) идентифицирует делегата, чья подпись соответствует аргументам, переданным методу пожара события.

Однако этот факт НЕ означает, что событие использует резервную копию поле делегата. По правде говоря, событие может использовать поле поддержки любого другого типа структуры данных по вашему выбору. Если вы явно реализуете событие на C #, вы можете выбрать способ хранения обработчиков событий (обратите внимание, что обработчики событий являются экземплярами типа события, который, в свою очередь, является мандатным типом делегата --- из предыдущего наблюдения ). Но вы можете хранить эти обработчики событий (которые являются экземплярами делегатов) в структуре данных, такой как List или Dictionary или любое другое другое, или даже в поле делегирования поддержки. Но не забывайте, что не обязательно использовать поле делегата.

6
ответ дан Miguel Gamboa 31 August 2018 в 16:38
поделиться

Чтобы понять различия, вы можете посмотреть на это 2 примера

Пример с делегатами (в данном случае Action - это своего рода делегат, который не возвращает значение)

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

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

Чтобы использовать делегат, вы должны сделать что-то вроде этого:

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

Этот код работает хорошо, но у вас могут быть слабые места.

Например, если я напишу это:

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

с последней строкой кода, я переопределил предыдущие действия только с одним отсутствующим + (я использовал = вместо +=)

Еще одно слабое место в том, что каждый класс, который использует ваш класс Animal, может поднять RaiseEvent, просто называя его animal.RaiseEvent().

Чтобы избежать этих слабых мест, вы можете использовать events в c #.

Ваш класс Animal изменится следующим образом:

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"));
    }
}

для вызова событий

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

Различия:

  1. Вы не используете публичное свойство, а общедоступное поле (используя события, компилятор защищает ваши поля от нежелательного доступа)
  2. События не могут быть назначены напрямую. В этом случае это не приведет к предыдущей ошибке, которую я показал с переопределением поведения.
  3. Никто из вашего класса не может поднять событие.
  4. События могут быть включенным в объявление интерфейса, тогда как поле не может

Примечания:

EventHandler объявляется следующим делегатом:

public delegate void EventHandler (object sender, EventArgs e)

it принимает отправителя (типа объекта) и аргументы события. Отправитель имеет значение null, если оно исходит из статических методов.

Этот пример, который использует EventHandler<ArgsSpecial>, также может быть записан с использованием EventHandler.

Здесь / g0] для документации о EventHandler

78
ответ дан mungflesh 31 August 2018 в 16:38
поделиться

Вы также можете использовать события в объявлениях интерфейса, а не для делегатов.

6
ответ дан Paul Hill 31 August 2018 в 16:38
поделиться
90
ответ дан Robert Harvey 31 August 2018 в 16:38
поделиться

Событие в .net - это назначенная комбинация метода Add и метода Remove, оба из которых ожидают определенного типа делегата. Оба C # и vb.net могут автоматически генерировать код для методов добавления и удаления, которые будут определять делегата для хранения подписки на события и добавлять / удалять переданные в делегате в / из этого делегата. VB.net также будет автоматически генерировать код (с инструкцией RaiseEvent) для вызова списка подписки тогда и только тогда, когда он не пуст; по какой-то причине, C # не генерирует последнее.

Обратите внимание, что, хотя для управления подписками на события используется многоадресный делегат, это не единственный способ сделать это. С публичной точки зрения потенциальный подписчик событий должен знать, как позволить объекту знать, что он хочет получать события, но ему не нужно знать, какой механизм издатель будет использовать для повышения событий. Обратите также внимание на то, что, хотя кто-то, кто определил структуру данных событий в .net, по-видимому, думал, что должно быть общедоступное средство для их повышения, ни C #, ни vb.net не используют эту функцию.

4
ответ дан supercat 31 August 2018 в 16:38
поделиться

Чтобы определить о событии простым способом:

Событие является ссылкой для делегата с двумя ограничениями

  1. Невозможно вызвать непосредственно
  2. Не удается (например, eventObj = delegateMethod)

Выше два являются слабыми точками для делегатов и рассматриваются в событии. Полный образец кода, чтобы показать разницу в fiddler, находится здесь https://dotnetfiddle.net/5iR3fB .

Переключить комментарий между Event и Delegate и кодом клиента, который вызывает / присваивает значения делегировать, чтобы понять разницу

Вот встроенный код.

 /*
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
ответ дан Venkatesh Muniyandi 31 August 2018 в 16:38
поделиться

Covariance и Contravariance обеспечивают дополнительную гибкость для объектов делегата. С другой стороны, событие не имеет таких понятий.

  • Covariance позволяет назначить метод делегату, где возвращаемый тип метода является классом, который является производным от класса который задает тип возврата делегата.
  • Contravariance позволяет назначить метод делегату, где тип параметра метода является базовым классом класса, который указан как параметр делегат.
0
ответ дан vivek nuna 31 August 2018 в 16:38
поделиться
Другие вопросы по тегам:

Похожие вопросы: