事件不是字段 - 我不明白
本文关键字:明白 字段 事件 | 更新日期: 2023-09-27 18:32:29
在深入的C#(到目前为止是一本很好的书(中,Skeet解释说事件不是字段。我读了很多遍这一部分,我不明白为什么这种区别会有什么不同。
我是混淆事件和委托实例的开发人员之一。在我看来,它们是相同的。两者不都只是一种间接形式吗?我们可以同时进行多播。事件被设置为速记字段...确定。但是,我们正在添加或删除处理程序。将它们堆叠起来,以便在事件触发时调用。我们不对委托做同样的事情,将它们堆叠起来并调用调用吗?
其他答案基本上是正确的,但这是另一种看待它的方式:
我是混淆事件和委托实例的开发人员之一。在我看来,它们是相同的。
我想起了一句老话,只见树木不见森林。我所做的区别在于,事件比委托实例字段处于更高的"语义级别"。事件告诉类型的消费者"你好,我是一个喜欢在发生事情时告诉你的类型"。该类型源事件;这是其公共合同的一部分。
作为实现细节,该类如何选择跟踪谁有兴趣收听该事件,以及告诉订阅者该事件正在发生什么以及何时发生,这是该类的业务。它碰巧通常使用多播委托执行此操作,但这是一个实现细节。这是一个如此常见的实现细节,混淆两者是合理的,但我们确实有两个不同的东西:公共图面和私有实现细节。
同样,属性描述对象的语义:客户具有名称,因此 Customer 类具有 Name 属性。您可能会说"他们的名字"是客户的财产,但你永远不会说"他们的名字"是客户的字段;这是特定类的实现细节,而不是关于业务语义的事实。属性通常作为字段实现是类机制的私有细节。
也不是字段,尽管它们感觉像它们。 它们实际上是一对具有特殊语法的方法(getter 和 setter(。
事件同样是一对具有特殊语法的方法(订阅和取消订阅(。
在这两种情况下,您的类中通常都有一个私有的"支持字段",其中包含由 getter/setter/subscribe/取消订阅方法操作的值。 属性和事件都有一个自动实现的语法,编译器在其中为你生成支持字段和访问器方法。
目的也是相同的:属性提供对字段的受限访问,其中在存储新值之前运行一些验证逻辑。 事件提供对委托字段的受限访问,其中使用者只能订阅或取消订阅,不能读取订阅者列表,也不能一次替换整个列表。
让我们考虑声明事件的两种方法。
要么使用显式 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
标识符就像在类外部一样,因为编译器无法保证它知道存储订阅的字段或任何其他机制。
事件是委托的访问器。 就像属性是字段的访问器一样。 使用完全相同的实用程序,它可以防止代码弄乱委托对象。 与属性具有 get 和 set 访问器一样,事件具有 add 和 remove 访问器。
它的行为确实与属性有些不同,如果您不自己编写添加和删除访问器,则编译器会自动生成它们。 包括存储委托对象的私有支持字段。 类似于自动属性。
你不经常这样做,但这肯定不罕见。 .NET 框架通常这样做,例如,Winforms 控件的事件存储在 EventHandlerList 中,添加/删除访问器通过其 AddHandler(( 和 RemoveHandler(( 方法操作该列表。 优点是所有事件(有很多(只需要类中的一个字段。
我可以在前面的答案中添加,委托可以在命名空间范围内(类外部(内声明,并且事件只能在类内声明。这是因为委托是一个类!
另一个区别是,对于事件,包含类是唯一可以触发它的类。您可以通过包含类订阅/取消订阅它,但不能触发它(与委托相反(。所以也许你现在可以理解为什么约定是把它包装在一个protected virtual OnSomething(object sender, EventArgs e)
里。它是为了让后代能够覆盖射击的实施。