C# - 事件关键字的优势?

Posted

技术标签:

【中文标题】C# - 事件关键字的优势?【英文标题】:C# - Event keyword advantages? 【发布时间】:2013-08-25 12:14:00 【问题描述】:

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

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

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

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

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

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

public Event SomethingHappened  get; private set; 

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

TLDR:

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

【问题讨论】:

"other than for modifying how the delegate can be accessed" 这对你来说还不够吗?这足以让我使用它。此外,它有助于更​​有效地传达成员的语义意图。 见csharpindepth.com/Articles/Chapter2/Events.aspx 有点像比较属性和字段。 也可以看到***.com/a/10484998/430661。 这个问题似乎是题外话,因为它不是一个问题。这是一个咆哮。 它不是一个修饰符,它是一个访问器,它限制了外部代码可以用委托做的事情的数量。只需添加和删除事件处理程序,仅此而已。就像属性是字段的访问器一样。它可以阻止行为不端的代码破坏其他代码进行的事件订阅。 【参考方案1】:

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

使用event关键字的主要优势。您仅在原始委托上使用事件来防止从定义它的类的范围之外调用或清除委托,因为在事件的情况下,调用事件是该类的责任。外部实体不应该直接调用它(它们可以并且应该间接调用事件),也不应该“关心”是否有任何其他事件处理程序或涉及到它们(例如,通过分配一个完全该领域的新代表)。

希望允许子类触发事件的特定情况最常见的解决方法是让定义事件的类创建一个除了触发事件之外什么都不做的受保护方法。按照惯例,此类方法将与事件同名,但带有“On”前缀。

是的,您可以创建自己的类型,该类型在逻辑上表示一个事件,是委托的包装器,并将可以对该事件执行的功能限制为“应该”能够执行它们的功能(可能稍微使用与 C# event 关键字使用的规则不同。这是在其他没有 event 关键字(甚至可能是委托)的语言中经常使用的东西。C# 设计者只是意识到这是一种非常常见的模式,并认为将关键字添加到语言中以帮助最小化创建逻辑“事件”所需的样板代码是值得的。

使用event 关键字的另一个好处是,与仅将某种类型的委托作为属性相反,您可以使您的意图更加清晰。如果我只看到一个委托属性,则通常意味着它代表一种方法。是的,C# 中的所有委托都是多播委托,所以这不是真的,但是人们在事件之外利用该功能是不寻常的。人们认为 Action 代表一个动作,而不是动作列表。事件对于 C# 文档也有特殊处理。它们都是单独列出的,它们在 Visual Studio 中具有不同的图标等。这一切都有助于使使用该类的人一目了然地了解成员的意图和语义。

最后,event 关键字确保了多个线程之间的同步,这不是由Delegate 类执行的。如果多个线程同时向一个事件添加处理程序,event 关键字确保两者都被添加。如果您只是公开公开一个委托,那么一个委托可能会由于竞争条件而覆盖另一个委托,并最终导致一个处理程序掉到地板上。如果您推出自己的 Event 类,您可以提供此功能,但它既是更多样板代码,又很容易搞砸(要么导致竞争条件进入,要么过度同步导致性能损失)。

【讨论】:

感谢您提供有关同步的信息。不过,如果派生类可以触发这些事件,我仍然会更高兴。由于该限制,我发现它破坏了 OOP。示例:基类 Animal 有一个名为 WakeUp 的事件。当动物醒来时,它会升起。派生类 Monkey 将在与派生类 Dog 不同的条件下唤醒,因此它们应该能够根据自己的逻辑而不是 Animal 的逻辑触发此事件。荒谬的是,Animal 的每个事件都需要创建一个方法以便 Monkey 和 Dog 可以引发这些事件。 @MichaelYanni 当前的实现使您能够通过定义受保护的方法来允许子类触发事件。它赋予控制权。如果它总是允许您描述的内容,那么当有人想要阻止子类触发事件时会发生什么?他们不是真正的方法。 他们为事件添加了“添加”和“删除”。添加第三个称为“raise”。然后,为事件访问器添加访问修饰符的能力。对于“添加”和“删除”,它们的默认设置显然是公开的。 “raise”的默认值可以是私有的。不过,您可以更改它,从而为事件添加少量自定义。此外,“raise”的默认操作是 Invoke,如果这不明显的话。 同意。我只是不喜欢在代码中重复自己。我称这样的方法为“填充方法”。基本上是浪费代码空间。 @MichaelYanni 但这不是浪费;它明确表明您正在以比通常更高的级别公开某些内容。 这值得一提、明确并引起注意。写代码的人都不会浪费时间,阅读代码的人也不会浪费时间。【参考方案2】:

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

通常的处理方式不是公开字段,而是公开引发事件的方法。

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

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

【讨论】:

【参考方案3】:

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

更容易阅读,每个人都知道“事件”是什么意思 工作量少,您无需创建特殊函数来订阅和取消订阅委托变量 向具有“事件”关键字的委托添加或从中删除已添加“锁定”(同步)。 IDE / 框架可以解释“事件”并为您提供帮助

【讨论】:

以上是关于C# - 事件关键字的优势?的主要内容,如果未能解决你的问题,请参考以下文章

c#—事件

用 C++ 编写性能关键的 C# 代码

C#委托(delegateActionFuncpredicate)和事件

C#:啥是虚拟事件以及如何使用它们?

C# 中的委托与事件

C# 中的委托与事件