Как события C # работают за кулисами?
Вопрос
Я использую 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?На что это указывает?Следующее не сработает, потому что обработчик событий никогда не инициализируется:
[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;
}
}
Я заметил, что есть обработчик событий.CreateDelegate(...), но все сигнатуры метода предполагают, что это используется только для присоединения делегатов к уже существующему EventHandler через обычный ElementAddedEvent += новый 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
}
}
Размышления об этом заставили меня понять, что я на самом деле не понимаю, что происходит в рамках hod с событиями - и я хотел бы понять :)
Решение
Я довольно подробно описал это в статья, но вот краткое изложение, при условии, что вы достаточно довольны делегаты сами по себе:
- Событие - это просто метод "добавить" и метод "удалить", точно так же, как свойство на самом деле является просто методом "получить" и методом "установить".(Фактически, CLI также допускает метод "raise / fire", но C # никогда не генерирует его.) Метаданные описывают событие со ссылками на методы.
- Когда вы объявляете выездное мероприятие (как и ваш ElementAddedEvent) компилятор генерирует методы и частное поле (того же типа, что и делегат).Внутри класса, когда вы ссылаетесь на ElementAddedEvent, вы ссылаетесь на поле.Вне класса вы ссылаетесь на поле.
- Когда кто-либо подписывается на событие (с помощью оператора +=), которое вызывает метод add.Когда они отписываются (с помощью оператора -=), который вызывает команду remove.
Для событий, подобных полю, существует некоторая синхронизация, но в остальном добавление / удаление просто вызывает 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
опять же, если все подписчики удалены, поскольку это поведение делегата.Удалить.Если вы хотите, чтобы обработчик "no-op" подписался на ваше событие, чтобы избежать проверки на недействительность, вы можете сделать:
public EventHandler<EventArgs> ElementAddedEvent = delegate {};
Тот Самый
delegate {}
это просто анонимный метод, который не заботится о своих параметрах и ничего не делает.
Если вам что-то еще неясно, пожалуйста, спрашивайте, и я постараюсь помочь!
Другие советы
Под капотом события просто делегаты со специальными соглашениями о вызовах. (Например, вам не нужно проверять недействительность перед тем, как вызывать событие.)
В псевдокоде Event.Invoke () разбивается следующим образом:
Если у события есть слушатели Вызовите каждого слушателя синхронно в этом потоке в произвольном порядке.
Так как события являются многоадресными, они будут иметь ноль или более слушателей, находящихся в коллекции. CLR будет проходить через них, вызывая каждый в произвольном порядке.
Следует помнить, что обработчики событий выполняются в том же потоке, в котором происходит событие. Распространенная умственная ошибка - думать о них как о порождающем новый поток. Они этого не делают.