C#中事件的内部设计

本文关键字:内部 事件 | 更新日期: 2023-09-27 18:29:48

刚刚读完Jon Skeet关于活动和代表的文章,收到了一个问题。

让我们先说在代码中我声明一个事件

public event EventHandler MyEvent

然后我想以的方式在代码中提出它

if (MyEvent != null)
    Myevent(this,EvtArgs.Empty);

Jon说事实上MyEvent看起来是这样的:

private EventHandler _myEvent;
public event EventHandler MyEvent
{
    add
    {
        lock (this)
        {
            _myEvent += value;
        }
    }
    remove
    {
        lock (this)
        {
            _myEvent -= value;
        }
    }        
} 

问题是,当我比较MyEvent != null时,实际会发生什么?事实上,据我所知,它将_myEventnull进行了比较,但我不确定。

C#中事件的内部设计

如果实现自定义的添加/删除访问器,则首先无法将MyEvent与null进行比较,因为它将是一个事件-它没有这样的值,仅添加/删除访问者。您将不得不使用声明的字段(上面示例中的_myEvent)。

当您使用类似事件的字段时,您只能使用事件名称进行比较,在该字段中,您(实际上)得到了一个字段和一个具有相同名称的事件。(编译后的代码实际上不必为字段名重复使用事件名,但它必须像编译时那样看起来。)

请注意,使用此:

if (MyEvent != null)
    MyEvent(this,EvtArgs.Empty);

不是线程安全的,因为MyEvent可能在检查和调用之间变成null。您应该使用:

EventHandler handler = MyEvent;
if (handler != null)
{
    handler(this, EventArgs.Empty);
}

还要注意,关于this上类似字段的事件锁定的部分现在稍微过时了;在C#4中,使用无锁比较交换机制来实现线程安全。

当没有处理程序订阅MyEvent时,MyEvent将等于null。因此,在触发事件之前,首先要检查是否至少有一个处理程序订阅了它

在内部,事件是一组添加/删除方法,其支持字段类型为MulticastDelegate。当您在类外请求MyEvent并将此实例与null进行比较时,会得到此字段。

另请参阅这篇文章:C#事件是如何在幕后工作的?

编辑:此外,正如Jon已经指出的,如果您自己提供添加/删除方法,则不会得到字段。