C#:具有明确添加/删除的事件!=典型事件?

C#:具有明确添加/删除的事件!=典型事件?,c#,.net,events,C#,.net,Events,我已经声明了一个通用事件处理程序 public delegate void EventHandler(); 我在其中添加了扩展方法“RaiseEvent”: public static void RaiseEvent(this EventHandler self) { if (self != null) self.Invoke(); } 当我使用典型语法定义事件时 public event EventHandler TypicalEvent; 然后,我可以调用使用扩展方

我已经声明了一个通用事件处理程序

public delegate void EventHandler();
我在其中添加了扩展方法“RaiseEvent”:

public static void RaiseEvent(this EventHandler self)        {
   if (self != null) self.Invoke();
}
当我使用典型语法定义事件时

public event EventHandler TypicalEvent;
然后,我可以调用使用扩展方法而不会出现问题:

TypicalEvent.RaiseEvent();
但是当我用显式的add/remove语法定义事件时

private EventHandler _explicitEvent;
public event EventHandler ExplicitEvent {
   add { _explicitEvent += value; } 
   remove { _explicitEvent -= value; }
}
那么,使用显式添加/删除语法定义的事件上不存在扩展方法:

ExplicitEvent.RaiseEvent(); //RaiseEvent() does not exist on the event for some reason
当我将鼠标悬停在事件上,查看其显示的原因时:

事件“ExpliciteEvent”只能 出现在+=或的左侧 -=

为什么使用典型语法定义的事件与使用显式添加/删除语法定义的事件不同?为什么扩展方法不适用于后者?

EDIT:我发现我可以直接使用私有事件处理程序来解决它:

_explicitEvent.RaiseEvent();
但是我仍然不明白为什么不能像使用典型语法定义的事件那样直接使用事件。也许有人能启发我。

因为你可以这样做(这是一个非真实世界的示例,但它“有效”):

编译器如何知道它应该如何处理“ExplicitEvent.RaiseEvent();”? 回答:不可能

“ExpliciteEvent.RaiseEvent();”只是语法糖,只有在隐式实现事件时才可以断言它。

TypicalEvent的“普通”声明在编译器中使用了一些技巧。它创建一个事件元数据条目、添加和删除方法以及一个备份字段。当代码引用TypicalEvent时,编译器将其转换为对backing字段的引用;当外部代码引用TypicalEvent(使用+=和-=)时,编译器将其转换为对add或remove方法的引用

“显式”声明绕过了这个编译器的诡计。您正在详细说明添加和删除方法以及支持字段:实际上,正如TcKs指出的,甚至可能没有支持字段(这是使用显式表单的常见原因:请参见System.Windows.Forms.Control中的事件)。因此,编译器无法再悄悄地将对TypicalEvent的引用转换为对backing字段的引用:如果需要backing字段,即实际的委托对象,则必须直接引用backing字段:

_explicitEvent.RaiseEvent()
创建“类字段”事件时,如下所示:

public event EventHandler Foo;
编译器生成一个字段和一个事件。在声明事件的类的源代码中,每当您引用
Foo
时,编译器都会理解您引用的是字段。但是,该字段是私有的,因此每当您从其他类中引用
Foo
时,它都会引用该事件(以及添加/删除代码)

如果声明自己的显式添加/删除代码,则不会得到自动生成的字段。因此,您只有一个事件,不能在C#中直接引发事件-只能调用委托实例。事件不是委托实例,它只是一个添加/删除对

现在,您的代码包含以下内容:

public EventHandler TypicalEvent;
这仍然略有不同-它根本没有声明事件-它声明的是委托类型的公共字段
EventHandler
。任何人都可以调用它,因为该值只是一个委托实例。理解场和事件之间的区别很重要。你不应该写这种代码,就像我确信你通常不会有其他类型的公共字段,比如
string
int
。不幸的是,这是一个容易犯的错误,也是一个相对难以阻止的错误。只有注意到编译器允许您分配或使用另一个类的值,您才能发现它


有关更多信息,请参阅我的。

这是因为您没有正确地看待它。 逻辑与属性中的相同。 一旦设置了add/remove,它就不再是一个实际事件,而是一个公开实际事件的包装器(事件只能从类本身内部触发,因此您始终可以在本地访问实际事件)

在这两种情况下,具有get/set或add/remove属性的成员实际上并不包含任何数据。您需要一个“真正的”私有成员来包含实际数据。 这些属性只允许您在将成员暴露给外部世界时编写额外的逻辑

一个很好的例子说明了为什么要这样做,就是在不需要的时候停止额外的计算(没有人在听这个事件)

例如,假设事件由计时器触发,如果没有人注册到事件,我们不希望计时器工作:

private System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
private EventHandler _explicitEvent;
public event EventHandler ExplicitEvent 
{
   add 
   { 
       if (_explicitEvent == null) timer.Start();
       _explicitEvent += value; 
   } 
   remove 
   { 
      _explicitEvent -= value; 
      if (_explicitEvent == null) timer.Stop();
   }
}

您可能希望使用对象锁定添加/删除(事后考虑).

您如何知道编译器是“他”?只是开个玩笑,谢谢你的答案和解释得很好的例子。我喜欢这个网站如此快速的回答。这就是我回来的原因。而且。。。巨大的知识量。不完全-在类中,+=和-=仍将引用该字段。它指的是你在课外提到的事件。谢谢你的回答。你解释得很好。我自己已经找到了解决方法,但仍然感谢您提供了关于如何在显式事件上调用RaisEvent()的示例。Jon,感谢您的深入了解。记住使用事件的复杂性总是很好。谢谢Jon,我不久前已经读了你的文章,只是当我注意到这种“奇怪的行为”(当时对我来说)时,我还不太清楚使用扩展方法。因此事件的行为类似于属性。@Alex78191:很好-除了使用add/remove而不是get/set。我还发现blimac在这里的回答()很有用…您也不能从类外使用扩展方法。
private EventHandler _explicitEvent;
public event EventHandler ExplicitEvent {
   add { _explicitEvent += value; } 
   remove { _explicitEvent -= value; }
}

private double seconds; 
public double Hours
{
    get { return seconds / 3600; }
    set { seconds = value * 3600; }
}
private System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
private EventHandler _explicitEvent;
public event EventHandler ExplicitEvent 
{
   add 
   { 
       if (_explicitEvent == null) timer.Start();
       _explicitEvent += value; 
   } 
   remove 
   { 
      _explicitEvent -= value; 
      if (_explicitEvent == null) timer.Stop();
   }
}