繁体   English   中英

C# 事件在幕后如何工作?

[英]How do C# Events work behind the scenes?

我正在使用 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
    }
}

考虑到这一点,我意识到我并不真正了解事件背后发生了什么 - 我想了解 :)

我已经在一篇文章中详细描述了这一点,但这里是摘要,假设您对代表本身感到满意:

  • 事件只是一个“add”方法和一个“remove”方法,就像属性实际上只是一个“get”方法和一个“set”方法一样。 (实际上,CLI 也允许使用“raise/fire”方法,但 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 的行为。

  • 如果您希望“无操作”处理程序订阅您的事件,以避免无效检查,您可以执行以下操作:

     public EventHandler<EventArgs> ElementAddedEvent = delegate {};

    delegate {}只是一个匿名方法,它不关心它的参数并且什么都不做。

如果还有什么不清楚的,请追问,我会尽力提供帮助!

在幕后,事件只是具有特殊调用约定的委托。 (例如,您不必在引发事件之前检查无效性。)

在伪代码中, Event.Invoke() 分解如下:

如果事件有侦听器以任意顺序在此线程上同步调用每个侦听器。

由于事件是多播的,它们将有零个或多个侦听器,保存在一个集合中。 CLR 将遍历它们,以任意顺序调用它们。

要记住的一个重要警告是,事件处理程序与引发事件的线程在同一线程中执行。将它们视为产生新线程是一种常见的心理错误。 他们不。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM