Как C# Events работают за кулисами?

Я использую C#, .NET 3.5. Я понимаю, как использовать события, как объявлять их в моем классе, как подключать их откуда-то еще и т. д. Надуманный пример:

public class MyList
{
    private List<string> m_Strings = new List<string>();
    public EventHandler<EventArgs> ElementAddedEvent;

    public void Add(string value)
    {
        m_Strings.Add(value);
        if (ElementAddedEvent != null)
            ElementAddedEvent(value, EventArgs.Empty);
    }
}

[TestClass]
public class TestMyList
{
    private bool m_Fired = false;

    [TestMethod]
    public void TestEvents()
    {
        MyList tmp = new MyList();
        tmp.ElementAddedEvent += new EventHandler<EventArgs>(Fired);
        tmp.Add("test");
        Assert.IsTrue(m_Fired);
    }

    private void Fired(object sender, EventArgs args)
    {
        m_Fired = true;
    }
}

Однако то, что я делаю, нет понимаю, - это когда объявляется обработчик событий

public EventHandler<EventArgs> ElementAddedEvent;

Он никогда не инициализируется - так что же такое ElementAddedEvent? На что это указывает? Следующее не сработает, потому что EventHandler никогда не инициализируется:

[TestClass]
public class TestMyList
{
    private bool m_Fired = false;

    [TestMethod]
    public void TestEvents()
    {
        EventHandler<EventArgs> somethingHappend;
        somethingHappend += new EventHandler<EventArgs>(Fired);
        somethingHappend(this, EventArgs.Empty);
        Assert.IsTrue(m_Fired);
    }

    private void Fired(object sender, EventArgs args)
    {
        m_Fired = true;
    }
}

Я заметил, что существует EventHandler.CreateDelegate (...), но все сигнатуры методов предполагают, что он используется только для присоединения делегатов к уже существующему EventHandler через типичный ElementAddedEvent + = new EventHandler (MyMethod).

Я не уверен, что какие, который я пытаюсь сделать, поможет ... но в конечном итоге я хотел бы придумать абстрактный родительский DataContext в LINQ, дети которого могут регистрировать, какие типы таблиц они хотят "наблюдать", чтобы я мог иметь события такие как BeforeUpdate и AfterUpdate, но относятся к типам. Что-то вроде этого:

public class BaseDataContext : DataContext
{
    private static Dictionary<Type, Dictionary<ChangeAction, EventHandler>> m_ObservedTypes = new Dictionary<Type, Dictionary<ChangeAction, EventHandler>>();

    public static void Observe(Type type)
    {
        if (m_ObservedTypes.ContainsKey(type) == false)
        {
            m_ObservedTypes.Add(type, new Dictionary<ChangeAction, EventHandler>());

            EventHandler eventHandler = EventHandler.CreateDelegate(typeof(EventHandler), null, null) as EventHandler;
            m_ObservedTypes[type].Add(ChangeAction.Insert, eventHandler);

            eventHandler = EventHandler.CreateDelegate(typeof(EventHandler), null, null) as EventHandler;
            m_ObservedTypes[type].Add(ChangeAction.Update, eventHandler);

            eventHandler = EventHandler.CreateDelegate(typeof(EventHandler), null, null) as EventHandler;
            m_ObservedTypes[type].Add(ChangeAction.Delete, eventHandler);
        }
    }

    public static Dictionary<Type, Dictionary<ChangeAction, EventHandler>> Events
    {
        get { return m_ObservedTypes; }
    }
}


public class MyClass
{
    public MyClass()
    {
        BaseDataContext.Events[typeof(User)][ChangeAction.Update] += new EventHandler(OnUserUpdate);
    }

    public void OnUserUpdated(object sender, EventArgs args)
    {
        // do something
    }
}

Подумав об этом, я понял, что я действительно не понимаю, что происходит под ходом событий - и я хотел бы понять :)

Также смотрите мой ответ stackoverflow.com/questions/2598170/…

user168237 06.10.2010 18:21
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
41
1
18 963
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Я подробно описал это в статья, но вот краткое изложение, предполагая, что вы достаточно довольны самими делегаты:

  • Событие - это просто метод «добавить» и «удалить», точно так же, как свойство на самом деле является просто методом «получить» и «установить». (Фактически, CLI также позволяет использовать метод «поднять / запустить», но C# никогда не генерирует его.) Метаданные описывают событие со ссылками на методы.
  • Когда вы объявляете полевое событие (например, ваш ElementAddedEvent), компилятор генерирует методы и частное поле (того же типа, что и делегат). Внутри класса, когда вы ссылаетесь на ElementAddedEvent, вы имеете в виду поле. Вы имеете в виду поле вне класса.
  • Когда кто-либо подписывается на событие (с оператором + =), которое вызывает метод добавления. Когда они отказываются от подписки (с помощью оператора - =), вызывающего удаление.
  • Для событий, подобных полям, есть некоторая синхронизация, но в противном случае добавление / удаление просто вызывает Delegate.Комбинировать / Удалять, чтобы изменить значение автоматически сгенерированного поля. Обе эти операции присваиваются вспомогательному полю - помните, что делегаты неизменяемы. Другими словами, автогенерируемый код очень похож на этот:

    // Backing field
    // The underscores just make it simpler to see what's going on here.
    // In the rest of your source code for this class, if you refer to
    // ElementAddedEvent, you're really referring to this field.
    private EventHandler<EventArgs> __ElementAddedEvent;
    
    // Actual event
    public EventHandler<EventArgs> ElementAddedEvent
    {
        add
        {
            lock(this)
            {
                // Equivalent to __ElementAddedEvent += value;
                __ElementAddedEvent = Delegate.Combine(__ElementAddedEvent, value);
            }
        }
        remove
        {
            lock(this)
            {
                // Equivalent to __ElementAddedEvent -= value;
                __ElementAddedEvent = Delegate.Remove(__ElementAddedEvent, value);
            }
        }
    }
    
  • Начальное значение сгенерированного поля в вашем случае - null - и оно всегда будет снова становиться null, если все подписчики будут удалены, поскольку это поведение Delegate.Remove.

  • Если вы хотите, чтобы обработчик «no-op» подписался на ваше событие, чтобы избежать проверки на нуль, вы можете сделать:

    public EventHandler<EventArgs> ElementAddedEvent = delegate {};
    

    delegate {} - это просто анонимный метод, который не заботится о своих параметрах и ничего не делает.

Если что-то еще неясно, спрашивайте, и я постараюсь помочь!

Какое красивое лаконичное объяснение!

DOK 18.10.2008 00:19

@ dok1: Я уже довольно много раз объяснял события :) Это одна из самых плохо понимаемых областей C#, IMO.

Jon Skeet 18.10.2008 00:29

Чтобы убедиться, что я правильно понимаю - событие, подобное полю, фактически поддерживается делегатом. Если ни у кого нет + =, он равен нулю (поэтому мы должны проверять ноль). Первый + = на нем выполняет присваивание, а второй + = - Комбинировать?

Matt 18.10.2008 00:31

Да, события и сборка мусора являются наименее изученными областями, а также, вероятно, наиболее важными.

Scott Dorman 18.10.2008 00:51

Забавно: мне пришлось перенести часть моего кода C# на C++, и я не хотел уходить слишком далеко, поэтому я реализовал обработчики событий для C++: синтаксис не сопоставим с C#, но внутреннее устройство ОЧЕНЬ интересно: MT-sync , копирование перед вызовом и т. д.

mmmmmmmm 12.01.2009 23:58

Твердый напишите здесь, Джон Скит, погуглил и нашел это первым ... именно то, что мне было нужно.

dbobrowski 03.10.2011 16:20

@JonSkeet Я прочитал статью здесь. Утверждение События не являются экземпляром делегата я не мог понять. В приведенном выше сообщении public EventHandler<EventArgs> ElementAddedEvent = delegate {}; Похоже, я назначаю мне экземпляр делегата. Далее __ElementAddedEvent = Delegate.Remove(__ElementAddedEvent, value); это тоже своего рода назначение мне экземпляра делегата. Не могли бы вы пояснить заявление. Я новичок, и мне трудно это понять.

shanmugharaj 02.11.2016 18:03

@shansfk: Этот оператор не объявляет событие. Это объявление поля.

Jon Skeet 02.11.2016 18:07

Под капотом события - это просто делегаты со специальными соглашениями о вызовах. (Например, вам не нужно проверять аннулирование перед созданием события.)

В псевдокоде Event.Invoke () работает следующим образом:

Если у события есть слушатели Вызов каждого слушателя синхронно в этом потоке в произвольном порядке.

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

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

Если вы не будете осторожны, делать должен проверять нулевое значение перед тем, как инициировать событие. На самом деле события не являются делегатами больше, чем свойства являются полями. События просто инкапсулируют поведение «подписка / отказ от подписки».

Jon Skeet 18.10.2008 00:14

Сомневаюсь, что порядок звонков произвольный. Я верю, что это фифо

Odys 23.06.2012 20:00

Другие вопросы по теме