Сведения о вопросе

Henry

12:33, 4th August, 2020

Теги

c#   events   delegates   glossary    

В чем разница между делегатами и мероприятиями?

Просмотров: 530   Ответов: 11

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



  Сведения об ответе

repe

14:57, 27th August, 2020

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


  Сведения об ответе

repe

13:28, 27th August, 2020

Помимо синтаксических и операциональных свойств, существует также семантическое различие.

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

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

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


  Сведения об ответе

COOL

13:08, 26th August, 2020

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

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

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#.

Ваш класс животных изменится таким образом:

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)

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

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

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


  Сведения об ответе

JUST___

01:38, 17th August, 2020

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

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

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

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

  1. Публичная переменная делегата

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

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

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

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

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


  Сведения об ответе

DAAA

07:54, 29th August, 2020

Примечание: Если у вас есть доступ к C# 5.0 Unleashed , прочитайте "Limitations on Plain Use of Delegates" в Главе 18 под названием "Events", чтобы лучше понять различия между ними.


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

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

Предположим, у меня есть приложение WinForms с одним раскрывающимся окном. Раскрывающийся список привязан к List<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's OnPersonChanged method. This will in turn call all the methods assigned (i.e. subscribed to) to the delegate -- in this case `DetailView_PersonChanged`.
}

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

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

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

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

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

Пример 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, он покажет вам, что определение является просто универсальным делегатом ified с дополнительным объектом "sender":

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

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


  Сведения об ответе

COOL

23:06, 10th August, 2020

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


  Сведения об ответе

PIRLO

17:52, 6th August, 2020

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

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

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

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


  Сведения об ответе

darknet

23:29, 25th August, 2020

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

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


  Сведения об ответе

PAGE

23:49, 26th August, 2020

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

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

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

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

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

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

 /*
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();
    }


}


  Сведения об ответе

park

17:32, 23rd August, 2020

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

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


  Сведения об ответе

JUST___

09:35, 3rd August, 2020

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


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться