事件不是字段 - 我不明白

时间:2012-04-28 20:11:25

标签: c# events field

C# in depth(迄今为止的优秀书籍)中,Skeet解释事件不是字段。我多次阅读本节,但我不明白为什么这种区别有所不同。

我是混淆事件和委托实例的开发人员之一。在我看来,它们是一样的。 Aren只是一种间接的形式?我们可以组播两者。事件被设置为字段作为简写...确定。但是,我们正在添加或删除处理程序。将它们堆叠起来以便在事件触发时调用。不要让代表做同样的事情,叠加它们并调用invoke?

5 个答案:

答案 0 :(得分:46)

其他答案基本上是正确的,但这是另一种看待它的方法:

  

我是混淆事件和委托实例的开发人员之一。在我看来,它们是一样的。

我想到了一个关于没有看到森林树木的古老谚语。我所做的区别是事件处于比委托实例字段更高的“语义级别”。一个事件告诉消费者类型“你好,我是一个喜欢在发生事情时告诉你的类型”。类型来源事件;这是公共合同的一部分。

作为一个实现细节,该类如何选择跟踪谁有兴趣收听该事件,以及告诉订阅者该事件发生的内容和时间是该类的业务。通常使用多播委托进行此操作,但这是一个实现细节。这是一个常见的实现细节,将两者混淆是合理的,但我们确实有两个不同的东西:公共表面和私有实现细节。

类似地,属性描述对象的语义:客户具有名称,因此Customer类具有Name属性。您可能会说“他们的名字”是客户的财产,但您永远不会说“他们的名字”是客户的字段;这是特定类的实现细节,而不是关于业务语义的事实。通常将属性实现为字段是类机制的私有细节。

答案 1 :(得分:41)

属性也不是字段,尽管他们感觉像是这样。它们实际上是一对具有特殊语法的方法(getter和setter)。

事件类似于一对具有特殊语法的方法(订阅和取消订阅)。

在这两种情况下,您的类中通常都有一个私有的“支持字段”,它保存由getter / setter / subscribe / unsubscribe方法操纵的值。并且有一个自动实现的属性和事件语法,编译器为您生成支持字段和访问器方法。

目的也是相同的:属性提供对字段的受限访问,其中在存储新值之前运行某些验证逻辑。并且事件提供对代理字段的受限访问,其中消费者只能订阅或取消订阅,不能读取订阅者列表,也不能立即替换整个列表。

答案 2 :(得分:24)

让我们考虑一下声明事件的两种方法。

使用显式add / remove方法声明事件,或者声明没有此类方法的事件。

换句话说,你声明这样的事件:

public event EventHandlerType EventName
{
    add
    {
        // some code here
    }
    remove
    {
        // some code here
    }
}

或者你这样声明:

public event EventHandlerType EventName;

事情是,在某些方面它们是相同的,在其他方面,它们是完全不同的。

从外部代码的角度来看,即发布事件的类之外的代码,它们完全相同。要订阅活动,请调用方法。要取消订阅,您可以使用其他方法。

不同之处在于,在上面的第二个示例代码中,这些方法将由编译器为您提供,但是,它仍然是如此。要订阅该活动,请调用方法。

但是,在C#中执行此操作的语法是相同的,您可以执行以下操作:

objectInstance.EventName += ...;

或:

objectInstance.EventName -= ...;

因此,从“外部视角”来看,这两种方式完全没有区别。

但是,在课堂上,存在差异。

如果您尝试访问课程中的EventName 标识符,那么您实际上是指支持该属性的field ,但仅限于您使用未明确声明add / remove方法的语法。

典型的模式是这样的:

public event EventHandlerType EventName;

protected void OnEventName()
{
    var evt = EventName;
    if (evt != null)
        evt(this, EventArgs.Empty);
}

在这种情况下,当您引用EventName时,您实际上指的是包含EventHandlerType类型的委托的字段。

但是,如果你已经明确声明了add / remove方法,那么引用类中的EventName标识符就像在类之外一样,因为编译器不能保证它知道您存储订阅的字段或任何其他机制。

答案 3 :(得分:9)

事件是代理人的访问者。就像一个属性是一个字段的访问者。使用完全相同的实用程序,它可以防止代码弄乱委托对象。就像属性具有get和set访问器一样,事件具有添加和删除访问器。

它的行为与属性有所不同,如果您不自己编写添加和删除访问器,则编译器会自动生成它们。包括存储委托对象的私有备份字段。与自动属性类似。

你不经常这样做,但肯定并不罕见。 .NET框架通常这样做,例如Winforms控件的事件存储在EventHandlerList中,添加/删除访问器通过其AddHandler()和RemoveHandler()方法操作该列表。有了 all 的优点,事件(有很多)只需要在类中有一个字段。

答案 4 :(得分:0)

我可以在前面的答案中添加委托可以在命名空间范围内(在类之外)声明,并且事件只能在类中声明。 这是因为委托是一个类!

另一个区别是,对于事件,包含类是唯一可以触发它的类。 您可以通过包含类订阅/取消订阅它,但不能触发它(与代理相反)。 所以也许你现在可以理解为什么约定将它包装在protected virtual OnSomething(object sender, EventArgs e)中。后代能够覆盖射击的实现。