在C# in depth(迄今为止的优秀书籍)中,Skeet解释事件不是字段。我多次阅读本节,但我不明白为什么这种区别有所不同。
我是混淆事件和委托实例的开发人员之一。在我看来,它们是一样的。 Aren只是一种间接的形式?我们可以组播两者。事件被设置为字段作为简写...确定。但是,我们正在添加或删除处理程序。将它们堆叠起来以便在事件触发时调用。不要让代表做同样的事情,叠加它们并调用invoke?
答案 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)
中。后代能够覆盖射击的实现。