Подтвердить что ты не робот

Как события С# работают за кулисами?

Я использую С#,.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
    }
}

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

4b9b3361

Ответ 1

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

  • Событие - это просто метод "добавить" и метод "удалить", аналогично тому, как свойство действительно является методом "get" и "set". (На самом деле, CLI позволяет использовать метод "raise/fire", но С# никогда не генерирует это.) Метаданные описывают событие со ссылками на методы.
  • Когда вы объявляете полевое событие (например, ваш ElementAddedEvent), компилятор генерирует методы и личное поле (того же типа, что и делегат). В классе, когда вы ссылаетесь на ElementAddedEvent, вы ссылаетесь на это поле. Вне класса вы ссылаетесь на это поле.
  • Когда кто-либо присоединяется к событию (с оператором + =), который вызывает метод добавления. Когда они отписываются (с оператором - =), который вызывает удаление.
  • Для полевых событий есть некоторая синхронизация, но в остальном add/remove просто вызывает делегат. Combine/Remove, чтобы изменить значение автоматически сгенерированного поля. Обе эти операции присваивают области поддержки - помните, что делегаты являются неизменными. Другими словами, автогенерированный код очень похож на это:

    // Backing field
    // The underscores just make it simpler to see what 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 снова, если все подписчики будут удалены, так как это поведение делегата .Remove.

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

    public EventHandler<EventArgs> ElementAddedEvent = delegate {};
    

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

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

Ответ 2

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

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

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

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

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