我在 Stack Overflow 上看到了一些非常好的问题,涉及委托、事件以及这两个功能的 .NET 实现。特别提出一个问题,“C# 事件在幕后如何工作?”,给出了一个很好的答案,很好地解释了一些微妙的观点。

上述问题的答案说明了这一点:

声明类似字段的事件时 ...编译器生成方法 和私有字段(类型相同 作为代表)。在课堂上, 当您引用 ElementAddedEvent 时 你指的是这个领域。外面 类,你指的是 田

同一问题链接的 MSDN 文章(“现场活动”)补充道:

引发事件的概念是 完全等同于调用 事件所代表的代表 — 因此,没有特殊的语言 用于引发事件的构造。

为了进一步检查,我构建了一个测试项目,以便查看事件和委托编译为的 IL:

public class TestClass
{
    public EventHandler handler;
    public event EventHandler FooEvent;

    public TestClass()
    { }
}

我期待代表领域 handler 和事件 FooEvent 编译为大致相同的 IL 代码,并使用一些附加方法来包装对编译器生成的访问 FooEvent 场地。但生成的 IL 并不完全符合我的预期:

.class public auto ansi beforefieldinit TestClass
    extends [mscorlib]System.Object
{
    .event [mscorlib]System.EventHandler FooEvent
    {
        .addon instance void TestClass::add_FooEvent(class [mscorlib]System.EventHandler)
        .removeon instance void TestClass::remove_FooEvent(class [mscorlib]System.EventHandler)
    }

    .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
    {
        // Constructor IL hidden
    }

    .field private class [mscorlib]System.EventHandler FooEvent
    .field public class [mscorlib]System.EventHandler handler
}

由于事件只不过是编译器生成的委托 addremove 方法,我没想到会看到事件被比 IL 中的处理方式更多。但是添加和删除方法是在开始的部分中定义的 .event, , 不是 .method 和正常方法一样。

我的最终问题是:如果事件简单地实现为带有访问器方法的委托,那么拥有一个事件有什么意义呢? .event IL部分?如果没有这个,它们不能通过使用 IL 来实现吗? .method 部分?是 .event 相当于 .method?

有帮助吗?

解决方案

我不确定这是否令人惊讶......与属性与字段的相同内容进行比较(因为属性在与事件相同的函数之前:通过访问器封装):

.field public string Foo // public field
.property instance string Bar // public property
{
    .get instance string MyType::get_Bar()
    .set instance void MyType::set_Bar(string)
}

还有-活动 不要 提及有关字段的任何内容;他们 仅有的 定义访问器(添加/删除)。委托支持者是一个实现细节;碰巧的是,类似字段的事件将一个字段声明为支持成员 - 与自动实现的属性将一个字段声明为支持成员的方式相同。其他实现也是可能的(并且非常常见,尤其是在表单等中)。

其他常见的实现:

稀疏事件(控件等)-EventHandlerList(或类似):

// only one instance field no matter how many events;
// very useful if we expect most events to be unsubscribed
private EventHandlerList events = new EventHandlerList();
protected EventHandlerList Events {
    get { return events; } // usually lazy
}

// this code repeated per event
private static readonly object FooEvent = new object();
public event EventHandler Foo
{
    add { Events.AddHandler(FooEvent, value); }
    remove { Events.RemoveHandler(FooEvent, value); }
}
protected virtual void OnFoo()
{
    EventHandler handler = Events[FooEvent] as EventHandler;
    if (handler != null) handler(this, EventArgs.Empty);
}

(以上几乎是胜利形式活动的支柱)

外观(尽管这让“发送者”有点困惑;一些中间代码通常很有帮助):

private Bar wrappedObject; // via ctor
public event EventHandler SomeEvent
{
    add { wrappedObject.SomeOtherEvent += value; }
    remove { wrappedObject.SomeOtherEvent -= value; }
}

(以上也可用于有效地重命名事件)

其他提示

事件与代表不同。事件封装添加/删除事件的处理程序。处理程序用委托表示。

可以为每个事件编写AddClickHandler / RemoveClickHandler等 - 但这会相对痛苦,并且不会让像VS这样的工具轻松地将事件与其他事件分开。

这就像属性一样 - 您可以编写GetSize / SetSize等(就像在Java中一样),但通过分离属性,可以使用语法快捷方式和更好的工具支持。

拥有一对add,remove方法的事件是封装

大多数时间事件都按原样使用,但有时您不希望在事件中存储附加到事件的委托,或者您希望在添加或删除事件方法时执行额外处理。

例如,实现内存高效事件的一种方法是将代理存储在字典中而不是私有字段,因为字段总是被分配,而字典只在添加项目时增大。此模型类似于Winforms和WPF使用的内容使得有效使用内存(winforms和WPF使用键控字典来存储代理而不是列表)

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top