Я использую 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
}
}
Подумав об этом, я понял, что я действительно не понимаю, что происходит под ходом событий - и я хотел бы понять :)





Я подробно описал это в статья, но вот краткое изложение, предполагая, что вы достаточно довольны самими делегаты:
Для событий, подобных полям, есть некоторая синхронизация, но в противном случае добавление / удаление просто вызывает 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 {} - это просто анонимный метод, который не заботится о своих параметрах и ничего не делает.
Если что-то еще неясно, спрашивайте, и я постараюсь помочь!
Какое красивое лаконичное объяснение!
@ dok1: Я уже довольно много раз объяснял события :) Это одна из самых плохо понимаемых областей C#, IMO.
Чтобы убедиться, что я правильно понимаю - событие, подобное полю, фактически поддерживается делегатом. Если ни у кого нет + =, он равен нулю (поэтому мы должны проверять ноль). Первый + = на нем выполняет присваивание, а второй + = - Комбинировать?
Да, события и сборка мусора являются наименее изученными областями, а также, вероятно, наиболее важными.
Забавно: мне пришлось перенести часть моего кода C# на C++, и я не хотел уходить слишком далеко, поэтому я реализовал обработчики событий для C++: синтаксис не сопоставим с C#, но внутреннее устройство ОЧЕНЬ интересно: MT-sync , копирование перед вызовом и т. д.
Твердый напишите здесь, Джон Скит, погуглил и нашел это первым ... именно то, что мне было нужно.
@JonSkeet Я прочитал статью здесь. Утверждение События не являются экземпляром делегата я не мог понять. В приведенном выше сообщении public EventHandler<EventArgs> ElementAddedEvent = delegate {}; Похоже, я назначаю мне экземпляр делегата. Далее __ElementAddedEvent = Delegate.Remove(__ElementAddedEvent, value); это тоже своего рода назначение мне экземпляра делегата. Не могли бы вы пояснить заявление. Я новичок, и мне трудно это понять.
@shansfk: Этот оператор не объявляет событие. Это объявление поля.
Под капотом события - это просто делегаты со специальными соглашениями о вызовах. (Например, вам не нужно проверять аннулирование перед созданием события.)
В псевдокоде Event.Invoke () работает следующим образом:
Если у события есть слушатели Вызов каждого слушателя синхронно в этом потоке в произвольном порядке.
Поскольку события являются многоадресными, у них будет ноль или более слушателей в коллекции. CLR будет перебирать их в цикле, вызывая каждый в произвольном порядке.
Следует помнить одно большое предостережение: обработчики событий выполняются в том же потоке, в котором возникает событие. Распространенная ментальная ошибка - думать о них как о порождении нового потока. Они не.
Если вы не будете осторожны, делать должен проверять нулевое значение перед тем, как инициировать событие. На самом деле события не являются делегатами больше, чем свойства являются полями. События просто инкапсулируют поведение «подписка / отказ от подписки».
Сомневаюсь, что порядок звонков произвольный. Я верю, что это фифо
Также смотрите мой ответ stackoverflow.com/questions/2598170/…