【问题标题】:C#: is calling an event handler explicitly really "a good thing to do"?C#:显式调用事件处理程序真的是“一件好事”吗?
【发布时间】:2009-06-11 23:14:20
【问题描述】:

这个问题与 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 )”。

实际上,问题是,是否有任何广泛使用的样式指南支持或阻止上述用法?

【问题讨论】:

  • 是的,在我看来,这也不是好的做法。您的“HandleUserEvent”想法可能是解决它的好方法 - 尽管在大多数情况下您可能可以将其命名为更好的名称。

标签: c# .net formatting coding-style


【解决方案1】:

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

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。)

【讨论】:

    【解决方案2】:

    我同意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 的 cmets

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

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

    一些相关参考资料:

    【讨论】:

    • 我想我的问题的答案是肯定的,还有其他人认为这种风格是错误的。至于标准在哪里?我想这将是“依赖”。例如,取决于您是否遵循 MVC 设计。或者,如果您使用发布的问题中的代码样式,您为什么要这样做?或者甚至将其作为问题的答案发布,就好像您认可它一样?
    • @mpbloch:我在回答中添加了更多评论。您最初问题的答案是“是的,您发布的代码格式不正确。”还有,“不,你并不孤单。”我建议阅读一些关于软件设计和设计模式的好书(四本书,“设计模式”将是一个很好的开始),以便建立一些可以用来对付与你一起工作的反对者的抵押品。你的直觉是对的,现在去证明它并取得真正的进步!
    • @Daniel:感谢您对本书的更新和推荐。我一定会把它添加到我的借书架上说:“在这里,读这个”;-)(就像给一个臭孩子除臭剂[@code气味])
    【解决方案3】:

    myButton.PerformClick() 可能会更好一些,如果您不需要传递 eventargs。有时您只想模拟一次点击。

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

    然后,您可以重新排列和重新设计您的 UI,而不必太担心逻辑在哪里。

    【讨论】:

      【解决方案4】:

      如果另一个编码器在 myButton_Click 方法上工作,此代码会增加出现问题的机会。

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

      Button b = (Button)sender;
      

      如果不阅读其余的类实现,我不知道我并不总是收到一个按钮作为发送者。

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

      【讨论】:

        【解决方案5】:

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

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

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

        更进一步,遵循一些简单的实践作为基本重构并使用分层(n 层)方法来处理软件将使您走得更远,并且您会意识到您提供的代码闻起来很糟糕。

        最终您会遇到一些高级模式,例如 MVC(模型-视图-控制器)和 MVP(模型-视图-呈现器),它们超越了简单的分层。如果您遵循它们,您将获得良好的关注点分离。

        我同意接受的答案,但直接跳到“使用 MVC”,这里有一些代码没有说明 MVC,没有解释为什么对我来说有点货物崇拜。

        【讨论】:

          【解决方案6】:

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

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

          【讨论】:

          • 是的,但问题是从其他代码调用处理程序是否是一种好习惯。
          • 归结为设计决策,而不是物理上是否可行的问题
          • 如果 C# 应用程序需要担心堆栈加载,可能是因为有人一开始就选择了错误的语言。
          • 对于感兴趣的读者,.NET 将为调用其他方法的方法优化堆栈。我欢迎任何熟悉这种做法的人在此处提供链接。我在 MSDN 博客上阅读了它。如果我看到链接,我会回来编辑,然后把它留在这里。
          【解决方案7】:

          我知道这个问题已经很久没有问过了,但我会有所不同,说不。只要(1)它有效并且(2)它的作用很清楚。我听说过所有关于设计原则等的标准论点,但我认为有时人们会过分而忘记设计的基础知识,以免让事情变得过于复杂。我在强制 OOB 的人身上看到了这一点,而程序设计也确实更适合。

          一种解决方法是将代码移到别处并调用该函数,但任意移动代码的问题是它会产生不必要的重复,使其更复杂且可读性更低,这与良好的设计相反。按照让一个函数做一件事的方法,人们最终不得不从多个地方调用一堆函数,这只会真正成功地为错误和额外开销创造更多机会。

          我相信这就是我们在错误程序中看到的所有膨胀的原因。我更喜欢尽可能多地重用和分组,如果这包括直接调用函数而不是在有意义的地方间接调用函数并且做同样的事情,那么这实际上可以提高可读性并降低复杂性。

          【讨论】:

            猜你喜欢
            • 2010-11-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-05-25
            相关资源
            最近更新 更多