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

时间:2009-06-18 21:03:07

标签: c# .net events

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

public delegate void EventHandler();

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

public static void RaiseEvent(this EventHandler self)        {
   if (self != null) self.Invoke();
}

当我使用典型语法

定义事件时
public event EventHandler TypicalEvent;

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

TypicalEvent.RaiseEvent();

但是当我使用显式添加/删除语法

定义事件时
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

当我将鼠标悬停在活动上以查看原因时:

  

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

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

编辑:我发现我可以通过直接使用私人事件处理程序来解决这个问题:

_explicitEvent.RaiseEvent();

但我仍然不明白为什么我不能像使用典型语法定义的事件那样直接使用事件。也许有人可以启发我。

4 个答案:

答案 0 :(得分:50)

当您创建“类似字段”的事件时,如下所示:

public event EventHandler Foo;

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

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

现在,您的代码包含:

public EventHandler TypicalEvent;

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

有关详细信息,请参阅我的article on events and delegates

答案 1 :(得分:35)

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

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 */ }
   }
}

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

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

答案 2 :(得分: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; }
}

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

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

答案 3 :(得分:1)

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

“显式”声明绕过了这个编译器技巧。您正在拼写添加和删除方法以及支持字段:实际上,正如TcKs指出的那样,甚至可能 支持字段(这是使用显式表单的常见原因:请参阅eg System.Windows.Forms.Control中的事件。因此,编译器不能再将对TypicalEvent的引用安静地转换为对支持字段的引用:如果需要支持字段,实际的委托对象,则必须直接引用支持字段:

_explicitEvent.RaiseEvent()