Каковы различия между делегатами и событиями?

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

+278
источник поделиться
11 ответов

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

+251
источник

В дополнение к синтаксическим и эксплуатационным свойствам существует также семантическая разница.

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

События представляют... ну, события. Они предназначены для предупреждения кого-то, когда что-то происходит, и да, они придерживаются определения делегата, но это не одно и то же.

Даже если они были точно такими же (синтаксически и в коде IL), все равно останется семантическая разница. В общем, я предпочитаю иметь два разных имени для двух разных понятий, даже если они реализованы одинаково (это не значит, что мне нравится иметь один и тот же код дважды).

+91
источник

Чтобы понять различия, вы можете посмотреть на это 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 в С#.

Ваш класс 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();

Отличия:

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

Примечания:

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

public delegate void EventHandler (object sender, EventArgs e)

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

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

Обратитесь здесь для документации о EventHandler

+83
источник

Вот еще одна хорошая ссылка для ссылки. http://csharpindepth.com/Articles/Chapter2/Events.aspx

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

Цитата из статьи:

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

  1. Открытая переменная делегата

  2. Переменная делегата, поддерживаемая свойством

  3. Переменная делегата с методами AddXXXHandler и RemoveXXXHandler

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

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

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

+34
источник

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

+6
источник

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

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

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

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

+6
источник

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


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

Пример 1: Использование публичного делегата

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

enter image description here

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

  • 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 OnPersonChanged method. This will in turn call all the methods assigned (i.e. subscribed to) to the delegate -- in this case `DetailView_PersonChanged`.
}

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

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

  • Открытая переменная делегата (это то, что мы только что сделали выше, не делайте этого, я просто сказал вам, почему это плохо)
  • Поместите делегат в свойство с get/set (проблема в том, что подписчики могут переопределить друг друга, поэтому мы могли бы подписаться на кучу методов для делегата, а затем мы могли бы случайно сказать PersonChangedDel = null, уничтожив все из других подписок. Другая проблема, которая остается здесь, заключается в том, что, поскольку пользователи имеют доступ к делегату, они могут вызывать цели в списке вызовов - мы не хотим, чтобы внешние пользователи имели доступ к тому, когда нужно поднимать наши события.
  • Переменная делегата с методами 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
источник

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

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

+4
источник

Чтобы определить событие случайным образом:

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

  • Невозможно вызвать напрямую
  • Нельзя назначать значения напрямую (например, 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
источник

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

  • Covariance позволяет назначить метод делегату, где возвращаемый тип метода является классом, который является производным от класса, который указывает тип возврата делегата.
  • Contravariance позволяет назначить метод делегату, где тип параметра метода является базовым классом класса, который указан как параметр делегата.
0
источник

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

0
источник

Посмотрите другие вопросы по меткам или Задайте вопрос