C#:显式地调用一个事件处理程序真是“一件好事”吗?

时间:2009-06-11 23:14:21

标签: c# .net formatting coding-style

此问题与C#有关,但也可能适用于其他语言。我预计不会使用以下代码:

using System.Windows.Forms;

class MyForm : Form
{
    private Timer myTimer;
    private Button myButton;

    public MyForm()
    {
        // Initialize the components, etc.

        myTimer.Tick += new EventHandler( myTimer_Tick );
        myButton.Click += new EventHandler( myButton_Click );

        myTimer.Start();
    }

    private void myTimer_Tick( object sender, EventArgs eventArgs )
    {
        myTimer.Stop();
        // also, I see a lot of usage of 
        // Timer.Enabled = true/false instead of -^
        myButton_Click( this, ea /* or event EventArgs.Empty, or null */ );
        return;
    }

    private void myButton_Click( object sender, EventArgs eventArgs )
    {
        // do a lot of stuff, with lots of logic that doesn't even use the
        // state of the eventArgs
        return;
    }
}

我是一个人,因为上述风格是我的宠儿?还有其他人喜欢将事件处理与功能工作量分离的清晰度,甚至将复杂的例程分离成单独的功能吗?

是否有接受的风格?我觉得C#中的事件处理有任何表现力和灵活性可能会丢失这样的样式。我觉得如果你的方法意味着“点击了一个按钮”,那么只有在点击一个按钮时才会调用它。

对于那些写这样的人,我会说:如果你坚持使用EventHandler方法来处理你的计时器滴答,并点击你的按钮,那就称之为button_Click以外的东西 - 也许是“handleUserEvent( object sender, EventArgs eventArgs )

但实际上,问题是,是否有任何广泛使用的风格指南支持或阻止使用,如上所述?

6 个答案:

答案 0 :(得分:24)

这绝对不是“个人偏好”。对于如何编写结构良好,可维护,可重用且易于理解的代码,有一种清晰易懂的方法。代码中的每个方法都应该封装一个可重用的功能。代码的结构应该是:

void ButtonClickEventHandler(...)
{
    UserData userData = //determine user data from event data
    DoUserThing(userData);
}

void DoUserThing(UserData userData)
{
    //do stuff
}

void SomeOtherMethod()
{
    UserData userData = //get userdata from some other source
    DoUserThing(userData);
}

(这是一个非常宽松的例子。在正确的应用程序中,一切都应该是separated into different classes by concern。)

答案 1 :(得分:7)

我同意Rex M's answer,但我会更进一步。如果您正在使用MVC模式(或类似的东西),视图会将按钮单击委托给控制器。控制器方法当然可以从你班级的其他地方调用 - 比如从你的计时器回调中调用。

所以,回到原始代码:

using System.Windows.Forms;

class MyForm : Form
{
    private Timer myTimer;
    private Button myButton;

    private MyController myController;

    public MyForm()
    {
        // ...
        // Initialize the components, etc.
        // ...

        myTimer.Tick += new EventHandler( myTimer_Tick );
        myButton.Click += new EventHandler( myButton_Click );

        myTimer.Start();
    }

    private void myTimer_Tick( object sender, EventArgs eventArgs )
    {
        myTimer.Stop();
        myController.SomeMethod()
    }

    private void myButton_Click( object sender, EventArgs eventArgs )
    {
        // All the stuff done here will likely be moved 
        // into MyController.SomeMethod()
        myController.SomeMethod();
    }
}

使用MVC的一个优点是控制器与视图的分离。现在,控制器可以轻松地在多种视图类型中使用,并且退出的GUI更易于维护,因为它们包含非常少的应用程序逻辑。

编辑:添加以回应OP的评论

软件工程的基本设计原则谈论耦合和凝聚力。重要的是,我们努力最大限度地减少组件之间的耦合,同时最大化内聚力,因为这会导致更加模块化和可维护的系统。像MVC这样的模式和Open / Closed Principal等主体构建在这些基础上,为开发人员提供了更实际的实现模式。

因此,任何编写原始帖子中的代码的人都不了解软件设计的基础知识,需要大大提高他们的技能。应该赞扬OP识别这种“代码味道”,并试图理解为什么它不太正确。

一些相关的参考文献:

答案 2 :(得分:5)

如果你不需要传递eventargs,那么

myButton.PerformClick()可能会更好一些。有时你只想模拟一次点击。

但是,是的,我同意将真实代码转移到另一个函数更好。我更喜欢我的事件处理程序非常简单 - 只需将UI连接到其他地方的逻辑。

然后,您可以重新排列和重新设计您的用户界面,而无需担心逻辑的位置。

答案 3 :(得分:1)

如果另一个编码器使用myButton_Click方法,此代码会增加出现问题的几率。

如果我进入调整myButton.Click处理程序的实现怎么办?我可以假设发送者对象是一个Button,并尝试强制转换:

Button b = (Button)sender;

如果没有阅读其余的类实现,我并不知道我并不总是接收Button作为发送者。

所以我的观点是:-1的可维护性,因为打破了将作为myButton_Click参数传递的对象的假设。

答案 4 :(得分:1)

简短的回答是,为什么要通过直接调用处理程序来模拟按钮单击?如果您想将两种方法连接到同一事件,您只需将其连接起来即可。事件处理程序是多播委托,这意味着您可以添加多个委托。不止一次地举办活动是完全可以接受的。

    myTimer.Tick += myTimer_Tick;
    myTimer.Tick += myButton_Click;

    myButton.Click += myButton_Click;

这是否是WTF是我们无法通过短代码片段进行的工程调用。但是,根据你的评论,它闻起来像WTF。表单或任何UI都不应该处理业务逻辑。它们需要在某种程度上具有业务逻辑意识(如在验证中),但它们本身并不封装/强制执行逻辑。

更进一步,遵循一些简单的实践作为基本的重构,并使用分层(n层)的软件方法将带你走很长的路,你会意识到你提出的代码闻起来很糟糕。

最终你会遇到一些高级模式,比如MVC(模型 - 视图 - 控制器)和MVP(模型 - 视图 - 演示者),它们超越了简单的分层。如果你遵循它们就可以很好地分离问题。

我同意接受的答案,但是直接进入'使用MVC',这里有一些代码没有说明MVC,而没有解释为什么对我来说是一个小小的货币崇拜。

答案 5 :(得分:0)

关于C#中事件的特殊事情(和.Net框架一般是委托,它是函数指针的C / C ++等价物。附加到事件本身的方法在任何方面都不是特殊的,应该是可从任何地方调用。

更新: 也许我应该更加冗长,但我认为我使用“应该”而不是“可以”或“可能”就足够了。我的断言是,当需要实现它们所实现的功能时,应该调用事件处理程序,而不是让它们成为“执行工作”的方法的包装器,堆栈中调用的方法越少,就越好,特别是.Net异常处理的性能影响。