C#类订阅自己发布的事件是否是一种糟糕的形式?
我可能只是有点神经质,但我经常发现自己处于这样的情况:我有一个发布事件的类,我发现从类本身(例如在构造函数中)订阅这个事件很方便,而不仅仅是从外部类订阅 对我来说这听起来很合理,但我还是忍不住感到这是一种糟糕的做法,原因很简单,我总是面临这样一个问题:“为什么不在触发事件的代码中执行事件处理程序中提供的操作?”C#类订阅自己发布的事件是否是一种糟糕的形式?,c#,events,C#,Events,我可能只是有点神经质,但我经常发现自己处于这样的情况:我有一个发布事件的类,我发现从类本身(例如在构造函数中)订阅这个事件很方便,而不仅仅是从外部类订阅 对我来说这听起来很合理,但我还是忍不住感到这是一种糟糕的做法,原因很简单,我总是面临这样一个问题:“为什么不在触发事件的代码中执行事件处理程序中提供的操作?” 订阅自己的活动有什么缺点吗?我看这没有问题。但如果处理同一类中的事件,也可以重写事件方法: protected override void OnClick(Eventargs e) {
订阅自己的活动有什么缺点吗?我看这没有问题。但如果处理同一类中的事件,也可以重写事件方法:
protected override void OnClick(Eventargs e)
{
base.OnClick(e);
}
这会更有效,并使您能够在必要时吞下事件(只需不调用base.OnClick())。问题在于“someHandler”将更改对象的状态。是否希望在事件运行任何“外部”代码之前或之后更改此状态
public class Button
{
public Button()
{
this.Click += someHandler; // bad practice?
}
public event EventHandler Click;
public void HandleInput()
{
if (someInputCondition)
{
// Perform necessary actions here rather than
// subscribing in the constructor?
this.Click(this, ...);
}
}
}
如果订阅该事件,状态更改将在什么时候进行还不清楚,但是在“HandleInput()”中调用它可以更清楚地说明何时调用它
(另外,调用“HandleInput()”、“OnClick”并使其成为虚拟的,这样子类就可以重写它,这是更正常的做法)
在说了以上的话之后,订阅自己的活动通常不会有太大的伤害;在表示表单的UI类中,这是非常常见的,否则它会让很多阅读您的代码的人感到“惊讶”。在执行此操作时,由于内部优化,会产生非常严重的问题。由于优化,添加/删除事件处理程序不是线程安全的。它仅适用于声明类型使用的事件,如示例中所示
幸运的是,4.0已经改变了这一点,但如果您使用的是以前的版本,您可能会体验到这一点 如果以普通System.Windows.Form类为例,
当您想要处理表单加载事件(使用visual studio designer)时,它会被处理 在课堂上的形式本身
this.Load += new System.EventHandler(this.Form1_Load);
private void Form1_Load(object sender, EventArgs e)
{
}
所以我认为这根本不是问题 如果您的button类应该是第一个接收click事件的类,那么您应该在event方法中编写代码,例如:
protected virtual void OnClick(EventArgs e)
{
//insert your code here
if(this.Click != null)
{
this.Click(this, e);
}
}
但是,如果您的类不是第一个接收者,您可以正常订阅该事件
对我来说这听起来很合理,但我还是忍不住感到这是一种糟糕的做法,原因很简单,我总是面临这样一个问题:“为什么不在触发事件的代码中执行事件处理程序中提供的操作?”
为了回答这个问题,考虑部分类场景。假设您有一个基类型B。您运行一个自动工具,通过将B扩展到派生类D来装饰B。您的工具生成一个分部类,以便使用D的开发人员可以为自己的目的进一步自定义它
在这种情况下,当B声明的事件或D的机器生成端引发D的机器生成端时,D的用户编写端希望注册调用似乎是完全合理的
这就是多年前我们在设计VSTO时发现的情况。事实证明,在C#中实现这一点并不困难,但在VB中实现这一点却相当棘手。我相信VB已经对他们的事件订阅模型做了一些调整,使之更容易实现
也就是说:如果你能避免这种情况,我会的。如果您只是为内部订阅制作一个看起来像是坏代码味道的事件。C#3中的部分方法在这方面有很大帮助,因为它们使机器生成端在用户生成端调用少量通知函数变得简单和低成本,而不必费事发布事件。+1这是一个很好的观点,但在承包商或UI控件中订阅时这不是一个问题(也是单螺纹的)@Ian:我认为很少有人会遇到这种微妙的错误,但是很少有人会遇到这种错误。如果你在类的ctor中订阅自己的事件,那么就不可能遇到线程问题。此外,它允许你指定你的代码是在任何其他事件处理程序之前运行还是在aft之前运行erwards(或者两者兼而有之)。在我的例子中,这是我自己的自定义按钮类——与该类名没有特定的相关性;它可以是任何东西。因此没有可重写的OnClick方法(除非我的C#事件知识中有一个漏洞)。@vargonian:那么我建议你创建一个(
protected virtual void OnLoad(EventArgs e);
)。如果您没有将自己的按钮
类设计为支持继承,那么只需使用private void
。正如Flagbug指出的那样,这样做可以确保您能够控制外部事件的执行时间(即,它们是在类的内部单击处理代码之前还是之后执行的)。问题是mit OnLoad()不能保证只调用一次,并且您无法确切知道调用它的时间。@codymanix:您刚才所说的有几个缺陷。A)使用简单的bool
.B)可以很容易地防止OnLoad
处理多次OnLoad
将受到保护,因此不太可能多次调用它。如果您不信任您的用户,请将其处理部分设置为私有,并在调用OnLoad
.C)之前或之后调用它)不能保证事件只被调用一次,并且您无法确切地知道它何时被调用。但是,在创建UI表单时,您不希望类外部的任何人钩住这些事件。这是一个非常常见的“特殊情况”。为什么人们要包装事件处理程序i