Как C # Events работают за кулисами? - PullRequest
38 голосов
/ 18 октября 2008

Я использую 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).

Я не уверен, поможет ли то, что , которое я пытаюсь сделать, ... но в конечном итоге я хотел бы придумать абстрактный LINContext-родитель в 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
    }
}

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

Ответы [ 2 ]

66 голосов
/ 18 октября 2008

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

  • Событие - это просто метод «add» и метод «remove», точно так же, как свойство - это просто метод «get» и метод «set». (На самом деле, CLI также допускает метод «поднять / запустить», но C # никогда не генерирует это.) Метаданные описывают событие со ссылками на методы.
  • Когда вы объявляете поле-подобное событие (как ваш ElementAddedEvent), компилятор генерирует методы и закрытое поле (того же типа, что и делегат). Внутри класса, когда вы ссылаетесь на ElementAddedEvent, вы ссылаетесь на поле. Вне класса вы имеете в виду поле.
  • Когда кто-либо подписывается на событие (с оператором + =), которое вызывает метод add. Когда они отписываются (с оператором - =), который вызывает удаление.
  • Для событий, подобных полям, есть некоторая синхронизация, но в противном случае добавление / удаление просто вызывает 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 {} - это просто анонимный метод, который не заботится о своих параметрах и ничего не делает.

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

0 голосов
/ 18 октября 2008

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

В псевдокоде Event.Invoke () ломается так:

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...