7

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

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

当您声明类似字段的事件时......编译器会生成方法和私有字段(与委托的类型相同)。在类中,当您引用 ElementAddedEvent 时,您指的是该字段。在课堂之外,您指的是该领域

从同一问题(“ Field-like events ”)链接的 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 中看到事件被视为更多。但是 add 和 remove 方法是在开头的部分中定义的.event,而不是.method像普通方法那样。

我的终极问题是:如果事件被简单地实现为具有访问器方法的委托,那么拥有.eventIL 部分的意义何在?如果没有这个,它们不能通过使用.method部分在 IL 中实现吗?.event相当于.method? _

4

3 回答 3

7

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

.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; }
}

(上面也可以用来有效地重命名一个事件)

于 2008-10-20T20:41:25.320 回答
2

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

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

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

于 2008-10-20T20:41:34.870 回答
1

拥有一对添加、删除、方法的事件的重点是封装

大多数时候事件按原样使用,但其他时候您不想将附加到事件的委托存储在字段中,或者您想对添加或删除事件方法进行额外处理。

例如,实现内存高效事件的一种方法是将委托存储在字典而不是私有字段中,因为字段总是被分配,而字典仅在添加项目时才会增长。该模型与 Winforms 和 WPF 使用的模型相似,可以有效利用内存(winforms 和 WPF 使用键控字典来存储委托而不是列表)

于 2008-10-20T20:55:07.680 回答