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

42 .net c# events

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

public delegate void EventHandler();
Run Code Online (Sandbox Code Playgroud)

我添加了扩展方法'RaiseEvent':

public static void RaiseEvent(this EventHandler self)        {
   if (self != null) self.Invoke();
}
Run Code Online (Sandbox Code Playgroud)

当我使用典型语法定义事件时

public event EventHandler TypicalEvent;
Run Code Online (Sandbox Code Playgroud)

然后我可以调用使用扩展方法没有问题:

TypicalEvent.RaiseEvent();
Run Code Online (Sandbox Code Playgroud)

但是当我使用显式添加/删除语法定义事件时

private EventHandler _explicitEvent;
public event EventHandler ExplicitEvent {
   add { _explicitEvent += value; } 
   remove { _explicitEvent -= value; }
}
Run Code Online (Sandbox Code Playgroud)

那么使用显式添加/删除语法定义的事件上不存在扩展方法:

ExplicitEvent.RaiseEvent(); //RaiseEvent() does not exist on the event for some reason
Run Code Online (Sandbox Code Playgroud)

当我将鼠标悬停在活动上,看看它说的原因:

事件'ExplicitEvent'只能出现在+ =或 - =的左侧

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

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

_explicitEvent.RaiseEvent();
Run Code Online (Sandbox Code Playgroud)

但我仍然不明白为什么我不能直接使用事件,就像使用典型语法定义的事件一样.也许有人可以开导我.

Jon*_*eet 51

当您创建"类字段"事件时,如下所示:

public event EventHandler Foo;
Run Code Online (Sandbox Code Playgroud)

编译器生成一个字段一个事件.在声明事件的类的源代码中,只要您引用Foo编译器,就会明白您指的是该字段.但是,该字段是私有的,因此每当您Foo其他类引用时,它都会引用该事件(因此也就是添加/删除代码).

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

现在,您的代码包含:

public EventHandler TypicalEvent;
Run Code Online (Sandbox Code Playgroud)

这仍然略有不同 - 它根本没有声明事件 - 它声明了委托类型的公共字段EventHandler.任何人都可以调用它,因为该值只是一个委托实例.了解字段和事件之间的区别非常重要.你永远不应该写这种代码,就像我确定你通常没有其他类型的公共字段,如stringint.不幸的是,这是一个简单的拼写错误,而且是一个相对难以阻止的错误.您只会通过注意编译器允许您分配或使用其他类的值来发现它.

有关更多信息,请参阅我关于事件和代理的文章.


TcK*_*cKs 37

因为你可以这样做(它是非现实世界的样本,但它"有效"):

private EventHandler _explicitEvent_A;
private EventHandler _explicitEvent_B;
private bool flag;
public event EventHandler ExplicitEvent {
   add {
         if ( flag = !flag ) { _explicitEvent_A += value; /* or do anything else */ }
         else { _explicitEvent_B += value; /* or do anything else */ }
   } 
   remove {
         if ( flag = !flag ) { _explicitEvent_A -= value; /* or do anything else */ }
         else { _explicitEvent_B -= value; /* or do anything else */ }
   }
}
Run Code Online (Sandbox Code Playgroud)

编译器如何知道它应该用"ExplicitEvent.RaiseEvent();"做什么?答:不可以.

"ExplicitEvent.RaiseEvent();" 只是语法糖,只有在隐式实现事件时才可以预测.

  • 你怎么知道编译器是"他"?开个玩笑,谢谢你的答案以及解释它非常好的样本.我喜欢这个网站,因为这样快速的答案. (7认同)

Yoc*_*mer 8

那是因为你没有正确看待它.逻辑与Properties中的逻辑相同.一旦你设置了添加/删除它就不再是一个实际的事件,而是一个暴露实际事件的包装器(事件只能从类本身内部触发,所以你总是可以在本地访问真实事件).

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; }
}
Run Code Online (Sandbox Code Playgroud)

在这两种情况下,具有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();
   }
}
Run Code Online (Sandbox Code Playgroud)

您可能想要用对象锁定添加/删除(事后想法)......