事件不是字段 – 我不明白

在C#深入 (到目前为止一本优秀的书),Skeet解释事件不是领域 。 我多次阅读这个部分,我不明白为什么这个区别有什么不同。

我是混淆事件和委托实例的开发人员之一。 在我看来,他们是一样的。 不仅仅是一种间接的forms? 我们可以同时组播。 事件设置为一个字段作为速记…当然。 但是,我们正在添加或删除处理程序。 当事件发生时将它们堆叠起来。 我们不要和代表做同样的事情,把它们叠起来调用?

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

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

一个古老的说不看森林树木的想法。 我所做的区分是事件比代表实例的领域处于更高的“语义层次”。 一个事件告诉消费者types“你好,我是一个types,喜欢告诉你什么时候发生”。 types来源事件; 这是公共合同的一部分。

作为一个实现细节,这个类select跟踪谁有兴趣聆听这个事件,以及什么时候告诉订阅者这个事件正在发生,是这个类的业务。 它恰好通常使用多播委托来实现,但这是一个实现细节。 这是一个常见的实现细节,将两者混淆是合理的,但是我们真的有两个不同的东西:公共表面和私有实现细节。

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

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

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

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

目的也是一样的:属性提供对字段的有限访问权限,其中一些validation逻辑在存储新值之前运行。 而且,一个事件提供了对委托字段的有限访问,消费者只能订阅或取消订阅,不能读取订阅者列表,也不能一次性replace整个列表。

我们来考虑两种声明事件的方法。

要么使用显式的add / remove方法声明一个事件,要么声明一个没有这个方法的事件。

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

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

或者你这样声明:

 public event EventHandlerType EventName; 

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

从外部代码的angular度来看,那就是…在发布事件的类之外的代码,它们完全一样。 订阅一个事件,你可以调用一个方法。 要取消订阅,您可以调用不同的方法。

不同的是,在上面的第二个示例代码中,这些方法将由编译器为您提供,但是,这仍将是如何。 要订阅该事件,请调用一个方法。

但是,在C#中这样做的语法是相同的,你可以这样做:

 objectInstance.EventName += ...; 

要么:

 objectInstance.EventName -= ...; 

所以从“外部视angular”来看,两种方式根本没有什么不同。

但是,在课堂内部是有区别的。

如果您尝试访问类中的EventName 标识符 ,则实际上是指支持该属性的field但前提是您使用的语法不明确声明add / remove方法

典型的模式是这样的:

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

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

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

事件是代表的访问者 。 就像一个财产是一个领域的访问者。 使用完全相同的实用程序,它可以防止代码与委托对象混淆。 就像一个属性有一个get和set访问器,一个事件有add和remove访问器。

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

你不经常这样做,但肯定不是不寻常的。 .NET框架通常是这样做的,例如Winforms控件的事件存储在一个EventHandlerList中 ,而add / remove访问器通过它的AddHandler()和RemoveHandler()方法来处理这个列表。 由于所有的事件(有很多)只需要一个单一的领域在课堂上的优势。

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

另一个区别是,对于事件来说,包含类是唯一可以解雇的类。 您可以通过包含类来订阅/取消订阅,但不能解雇(与代表相反)。 所以也许你现在可以理解为什么约定是把它包装在一个protected virtual OnSomething(object sender, EventArgs e) 。 这是后代能够推翻执行的射击。