【问题标题】:C#: event with explicity add/remove != typical event?C#:带有明确添加/删除的事件!=典型事件?
【发布时间】:2010-11-04 03:35:54
【问题描述】:

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

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();

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

【问题讨论】:

  • 你也不能在类外使用扩展方法。

标签: c# .net events


【解决方案1】:

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

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();”只是语法糖,只有隐式实现事件才能谓词。

【讨论】:

  • 你怎么知道编译器是“他”?开个玩笑,感谢您的回答和很好地解释它的示例。我喜欢这个网站,因为它提供了这样的快速答案。
  • +1 超快的东西。这就是让我回来的原因。那和...大量的知识。
【解决方案2】:

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

“显式”声明绕过了这个编译器诡计。您正在拼写 add 和 remove 方法以及支持字段:实际上,正如 TcKs 指出的那样,甚至可能没有 be 支持字段(这是使用显式形式的常见原因:参见例如System.Windows.Forms.Control 中的事件)。因此编译器不能再悄悄地把对TypicalEvent的引用翻译成对支持字段的引用:如果你想要支持字段,实际的委托对象,你必须直接引用支持字段:

_explicitEvent.RaiseEvent()

【讨论】:

  • 不完全 - 在类中,+= 和 -= 将仍然引用该字段。当你从类外引用它时,它指的是事件。
  • 感谢您的回答。你解释得很好。我自己已经找到了解决方法,但仍然感谢有关如何在显式事件上调用 RaisEvent() 的示例。
  • 乔恩,感谢您的洞察力。记住使用事件的复杂性总是好的。
【解决方案3】:

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

public event EventHandler Foo;

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

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

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

public EventHandler TypicalEvent;

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

更多信息请查看我的article on events and delegates

【讨论】:

  • 谢谢乔恩,我前段时间已经阅读了你的文章,当我注意到扩展方法的这种“奇怪行为”(当时对我来说)时,我并没有立即明白。
  • 所以事件就像财产一样。
  • @Alex78191: 不错——除了添加/删除而不是获取/设置。
  • 我还发现 blmac 的回答在这里 (stackoverflow.com/questions/11180068/…) 很有帮助...
【解决方案4】:

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

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();
   }
}

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-21
    • 2016-07-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多