34

我最近开始明白 C# 的“事件”确实是。老实说,这不是什么。总结一下我的发现:event关键字只是一个仅适用于委托的修饰符。

因此,事件的所有“魔力”都是委托的操作。而已。我已经阅读了很多 Microsoft 文档,但没有一个句子可以如此简洁地总结。继续我的发现,委托、类和结构都在同一个“级别”上。它们是定义“对象”的方法。我不是指类型中的“对象”,而是“某物”的封装概念。就像在说面向对象编程时如何使用“对象”一词一样。

无论如何,“对象”具有某些修饰符。例如,sealed、readonly、virtual、static 等……这个列表可以在这里找到。在委托的情况下,它有一个额外的称为event。Event 使得当一个委托被声明为类的一部分时,它只根据给定事件的访问修饰符公开addremove方法。这些方法的定义与获取设置的性质相似的一个属性。委托的其他操作(赋值、读取访问、方法调用等)只允许在声明事件委托的类中进行。我觉得有趣的另一件事是,所有委托都有方法 Invoke、BeginInvoke 和 EndInvoke,但是您无法在 Visual Studio 中导航以查看它们,也找不到描述它们的文档......

好的。那么在了解了这一切之后,除了修改委托的访问方式之外,使用 event 关键字还有什么好处呢?看起来,在许多情况下,我最好简单地声明一个没有 event 关键字的委托。我最近遇到的一种情况是我想创建一个包含 2 个事件的抽象基类。从这个基类派生的任何类都应该能够像它们自己一样使用事件,类似于暴露给派生类的类的任何其他对象(也称为非私有,除非派生类在另一个程序集,并且该对象被声明为内部的)。

基本上,我希望派生类将这些事件用作自己的事件。这样做的唯一方法是将事件的支持变量公开为受保护的,因此派生类可以引发事件。查看代码,这似乎很愚蠢,因为我基本上定义了两次委托;一次作为受保护的字段,另一次作为公共事件。我想,

在构造函数中创建一个具有 Action 的 out 参数的名为 Event 的类不是更好吗?返回的操作相当于许多人作为委托的扩展方法所做的 Raise,它检查委托是否为空,然后调用委托。Event 上唯一的公共方法是 Add 和 Remove,用于附加委托并将它们从底层委托 (+=、-=) 中删除。类可以将这些事件作为属性,例如,

public Event SomethingHappened { get; private set; }

这样只有那个班级才能重新分配事件。或者公共只读字段也同样有效。从构造函数返回的 out 参数由类存储并在类想要引发事件时调用。我知道这是一个很糟糕的解决方法,但它会完成工作,并且允许事件不仅作为参数传递,而且如果基类将其定义为受保护,则允许派生类调用 Raise 方法。

TLDR:

除了修改委托的访问方式之外,使用 event 关键字还有什么好处?

4

3 回答 3

25

What is the advantage of using the event keyword other than for modifying how the delegate can be accessed?

That is the primary advantage of using the event keyword. You use an event over just a raw delegate to prevent the delegate from being invoked or cleared from outside the scope of the class it is defined in, because in the case of events it is the responsibility of that class to invoke the event. External entities shouldn't be invoking it directly (they can and should be invoking the event indirectly), nor should they "care" about whether there are any other event handlers or be involved in touching them (by, for example, assigning an entirely new delegate to the field).

The specific case of wanting to allow sub-classes to fire the event is most commonly solved by having the class that defines the event creating a protected method that does nothing but fire the event. Such methods will, by convention, have the same name as the event but with "On" prefixing it.

Yes, you could create your own type that logically represents an event, is a wrapper for a delegate, and limits the functions that can be performed on that event to those that "should" be able to perform them (possibly using slightly different rules than the C# event keyword uses. This is something that is frequently used in other languages that don't have an event keyword (or possibly even delegates). The C# designers simply realized that this was a very common pattern, and felt that it was worth the energy to add the keyword to the language to help minimize the boilerplate code required to create a logical "event".

Another benefit of using the event keyword, as opposed to just having some type of delegate as a property, is that you make your intentions much clearer. If I see just a delegate property the implication is generally that it represents one method. Yes, all delegates in C# are multicast delegates, so that's not true, but it's unusual for people to leverage that functionality outside of events. People think that an Action represents one action, not a list of actions. Events also have special treatment with respect to the C# documentation. They are all listed separately, they have different icons in visual studio, etc. This all helps make the intentions and semantics of the member much clearer to someone using the class at a glance.

Finally, the event keyword ensures that there is synchronization between multiple threads, which isn't performed by the Delegate class. If multiple threads go to add handlers to an event at the same time, the event keyword ensures both are added. If you just publicly expose a delegate it's possible for one to overwrite the other due to a race condition and have one handler end up dropped on the floor. If you roll your own Event class you could provide this functionality, but it is both more boilerplate code and something that's pretty darn easy to mess up (either resulting in leaving race conditions in, or excessive synchronization resulting in lost performance).

于 2013-08-22T16:45:11.027 回答
3

基本上,我希望派生类将这些事件用作自己的事件。这样做的唯一方法是将事件的支持变量公开为受保护的,因此派生类可以引发事件。

处理这种情况的常用方法不是公开字段,而是公开引发事件的方法。

public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) {
  var handler = PropertyChanged;
  if (handler != null)
    handler(this, e);
}

这不仅允许派生类引发事件,而且还允许派生类在任何订阅的处理程序实际被调用之前做一些事情。

但是,要回答您的实际问题:

除了修改委托的访问方式之外,使用 event 关键字还有什么好处?

尚未提及的一个优点(我认为):

public event PropertyChangedEventHandler PropertyChanged;

可以改为

public event PropertyChangedEventHandler PropertyChanged
{
  add { /* custom code here */ }
  remove { /* custom code here */ }
}

无需重新编译库的所有用户。如果您以后发现有理由不将处理程序简单地存储在私有字段中,您可能会想要这样的东西。这与自动实现的属性相对于字段的优势相同。

于 2013-08-22T16:55:50.727 回答
0

我认为您可以轻松地将“事件”关键字与访问器进行比较,但您还可以从“事件”关键字中获得一些其他好处:

  • 更容易阅读,每个人都知道“事件”是什么意思
  • 更少的工作,你不需要创建特殊的函数来订阅和取消订阅委托变量
  • 添加到具有“事件”关键字的委托或从委托中删除已添加“锁定”(同步)。
  • IDE / 框架可以解释“事件”并帮助您
于 2014-05-09T15:48:08.170 回答