题
我正在使用 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(...),但所有方法签名表明这仅用于通过典型的 ElementAddedEvent += new EventHandler(MyMethod) 将委托附加到已存在的 EventHandler。
我不确定是否 什么 我正在努力做的事情会有所帮助...但最终我想在 LINQ 中提出一个抽象父 DataContext,其子级可以注册他们想要“观察”的表类型,以便我可以拥有诸如 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
}
}
思考这一点让我意识到我并不真正了解事件背后发生的事情 - 我想了解:)
解决方案
我已经相当详细地写了这个 一篇文章, ,但这里是摘要,假设您对以下内容相当满意 代表们 他们自己:
- 事件只是一个“添加”方法和一个“删除”方法,就像属性实际上只是一个“获取”方法和“设置”方法一样。(事实上,CLI 也允许“引发/触发”方法,但 C# 从不生成此方法。)元数据通过对方法的引用来描述事件。
- 当你声明一个 场类事件 (如您的 ElementAddedEvent)编译器生成方法 和一个私人领域 (与委托的类型相同)。在类中,当您引用 ElementAddedEvent 时,您指的是该字段。在课堂之外,你指的是现场。
- 当任何人订阅调用 add 方法的事件(使用 += 运算符)时。当他们取消订阅(使用 -= 运算符)时,调用remove。
对于类似字段的事件,存在一些同步,但添加/删除仅调用委托。结合/消除 更改自动生成字段的值。这两个操作都分配给支持字段 - 请记住委托是不可变的。换句话说,自动生成的代码非常像这样:
// 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 的行为。如果您希望“无操作”处理程序订阅您的事件,以避免无效检查,您可以执行以下操作:
public EventHandler<EventArgs> ElementAddedEvent = delegate {};
这
delegate {}
只是一个匿名方法,它不关心它的参数,也不执行任何操作。
如果还有什么不清楚的地方,请提问,我会尽力帮助!
其他提示
在幕后,事件只是具有特殊调用约定的委托。 (例如,您不必在引发事件之前检查无效。)
在伪代码中,Event.Invoke()会像这样分解:
如果事件有听众 以任意顺序在此线程上同步调用每个侦听器。
由于事件是多播的,因此它们将拥有零个或多个侦听器,并保存在集合中。 CLR将遍历它们,以任意顺序调用每个。
要记住的一个重要警告是事件处理程序在引发事件的同一个线程中执行。将它们视为产生新线程是一种常见的心理错误。他们没有。