【问题标题】:Events aren't fields - I don't get it事件不是字段 - 我不明白
【发布时间】:2012-05-23 07:54:14
【问题描述】:

C# in depth(迄今为止的一本优秀书籍)中,Skeet 解释了事件不是字段。这部分我读了很多遍,我不明白为什么这种区别有任何区别。

我是那些混淆事件和委托实例的开发人员之一。在我看来,它们是一样的。两者都不只是一种间接形式吗?我们可以同时多播。一个事件被设置为一个字段作为速记......当然。但是,我们正在添加或删除处理程序。将它们堆叠起来以在事件触发时调用。我们不是对委托做同样的事情,将它们堆叠起来并调用调用吗?

【问题讨论】:

  • 哇;你已经吸引了相当多的“重量”来回答你的问题! ;)
  • 非常感谢各位高手的观点。
  • 请注意,在挖掘勘误表后,我发现 Jon 确实阐述了这个主题(只是书中给出的链接不正确):csharpindepth.com/Articles/Chapter2/Events.aspx

标签: c# events field


【解决方案1】:

其他答案基本正确,不过换个角度来看:

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

想起一句老话,见树不见林。我所做的区别是事件比委托实例的字段处于更高的“语义级别”。一个事件告诉该类型的消费者“嗨,我是一个喜欢在发生某事时告诉你的类型”。类型来源一个事件;这是其公共合同的一部分。

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

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

【讨论】:

  • 那么 1. 代表不能做什么,但事件可以(反之亦然) 2. 代表不应该做什么,事件应该做什么。这可能会使差异更加清晰。
  • @Dhananjay 事件必须以某种方式使用委托,因为它们是作为一对将委托作为参数的方法(添加和删除)实现的。但是,它们不需要直接映射到委托字段。相反,他们可以以其他方式存储委托,或者他们可能创建一个包装委托并存储它,等等。类的用户不应该能够区分使用委托支持字段的自动事件和自定义编码事件,因此合理的实现选项有点有限。
  • Tangential:如果您可以从头开始重新启动 C# 和 .NET,您会用 IObservable 之类的东西替换事件/委托吗?
  • 您可以使用委托代替事件,但事件提供了更多的安全性,例如保护委托不被从定义类之外设置为空。我相信这是最大的不同。
【解决方案2】:

属性也不是字段,尽管它们看起来很像。它们实际上是一对具有特殊语法的方法(getter 和 setter)。

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

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

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

【讨论】:

  • 帮助理解这一点的经典示例是在使用诸如 winforms 之类的东西时 - 有一个 EventHandlerList 是大多数事件的“支持者”(可以通过 .Events 获得) , 子类使用)。只有 1 个字段 - 许多事件。
  • 我相信这是通过覆盖事件上的 addremove 方法来完成的 - 例如msdn.microsoft.com/en-us/library/ak9w5846.aspx的第二个例子
  • @dsolimano “覆盖”在这里使用可能是一个令人困惑的术语,因为它通常与多态性有关; 提供定制 add/remove,当然 - 有点像自动实现的属性和常规属性之间的区别(类似于“自动实现的属性”的事件术语是“field-喜欢事件”,顺便说一句)
  • 这是一个了不起的答案 +1!
【解决方案3】:

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

要么使用显式 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 类型的delegate 的字段。

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

【讨论】:

    【解决方案4】:

    事件是委托的访问器。就像属性是字段的访问器一样。使用完全相同的实用程序,它可以防止代码与委托对象混淆。就像属性有 get 和 set 访问器一样,事件也有 add 和 remove 访问器。

    它的行为确实与属性有些不同,如果您不自己编写 add 和 remove 访问器,那么编译器会自动生成它们。包括一个存储委托对象的私有支持字段。类似于自动属性。

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

    【讨论】:

    • 事件是类型为委托类型的字段的访问器。
    • 阅读答案以了解情况并非如此。最后一段。
    • 这和你答案中的第二句话一样真实。
    • Skeet 的观点是事件是提供对值的访问的方法,而不是值本身。就像一个属性,但有不同的操作。该值通常但不总是存储在字段中。再次,就像一个财产。说一个事件可能没有支持字段所涉及的迂腐程度也排除了做出一般性声明,即属性是字段的访问器。
    • 呃,你太糟糕了。没有字节。
    【解决方案5】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-09-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-18
      • 2011-09-22
      • 2011-06-26
      相关资源
      最近更新 更多