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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/213638/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 18:24:42  来源:igfitidea点击:

How do C# Events work behind the scenes?

c#events.net-3.5delegates

提问by Matt

I'm using C#, .NET 3.5. I understand how to utilize events, how to declare them in my class, how to hook them from somewhere else, etc. A contrived example:

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

However, what I do notunderstand, is when one declares an event handler

然而,我明白的是,当一个人声明一个事件处理程序时

public EventHandler<EventArgs> ElementAddedEvent;

It's never initialized - so what, exactly, is ElementAddedEvent? What does it point to? The following won't work, because the EventHandler is never initialized:

它从未被初始化——那么到底什么是 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;
    }
}

I notice that there is an EventHandler.CreateDelegate(...), but all the method signatures suggest this is only used for attaching Delegates to an already existing EventHandler through the typical ElementAddedEvent += new EventHandler(MyMethod).

我注意到有一个 EventHandler.CreateDelegate(...),但所有方法签名都表明这仅用于通过典型的 ElementAddedEvent += new EventHandler(MyMethod) 将委托附加到已经存在的 EventHandler。

I'm not sure if whatI am trying to do will help... but ultimately I'd like to come up with an abstract parent DataContext in LINQ whose children can register which table Types they want "observed" so I can have events such as BeforeUpdate and AfterUpdate, but specific to types. Something like this:

我不知道是什么,我试图做将帮助......但最终我想拿出在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
    }
}

Thinking about this made me realize I don't really understand what's happening under the hod with events - and I would like to understand :)

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

采纳答案by Jon Skeet

I've written this up in a fair amount of detail in an article, but here's the summary, assuming you're reasonably happy with delegatesthemselves:

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

  • An event is just an "add" method and a "remove" method, in the same way that a property is really just a "get" method and a "set" method. (In fact, the CLI allows a "raise/fire" method as well, but C# never generates this.) Metadata describes the event with references to the methods.
  • When you declare a field-like event(like your ElementAddedEvent) the compiler generates the methods and a private field(of the same type as the delegate). Within the class, when you refer to ElementAddedEvent you're referring to the field. Outside the class, you're referring to the field.
  • When anyone subscribes to an event (with the += operator) that calls the add method. When they unsubscribe (with the -= operator) that calls the remove.
  • For field-like events, there's some synchronization but otherwise the add/remove just call Delegate.Combine/Removeto change the value of the auto-generated field. Both of these operations assign to the backing field - remember that delegates are immutable. In other words, the autogenerated code is very much like this:

    // 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);
            }
        }
    }
    
  • The initial value of the generated field in your case is null- and it will always become nullagain if all subscribers are removed, as that is the behaviour of Delegate.Remove.

  • If you want a "no-op" handler to subscribe to your event, so as to avoid the nullity check, you can do:

    public EventHandler<EventArgs> ElementAddedEvent = delegate {};
    

    The delegate {}is just an anonymous method which doesn't care about its parameters and does nothing.

  • 事件只是一个“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 {}只是一个匿名方法,它不关心它的参数并且什么都不做。

If there's anything that's still unclear, please ask and I'll try to help!

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

回答by Yes - that Jake.

Under the hood, events are just delegates with special calling conventions. (For example, you don't have to check for nullity before raising an event.)

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

In pseudocode, Event.Invoke() breaks down like this:

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

If Event Has Listeners Call each listener synchronously on this thread in arbitrary order.

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

Since events are multicast, they will have zero or more listeners, held in a collection. The CLR will loop through them, calling each in an arbitrary order.

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

One big caveat to remember is that event handlers execute in the same thread as the event is raised in. It's a common mental error to think of them as spawning a new thread. They do not.

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