Jon*_*tus 117 c# exception-handling winforms
我目前正在编写我的第一个Windows Forms应用程序.我现在已经阅读了一些C#书籍,所以我对C#处理异常的语言功能有了比较深入的了解.它们都非常理论化,所以我还没有想到如何在我的应用程序中将基本概念转换为一个良好的异常处理模型.
有人愿意分享关于这个主题的任何智慧珍珠吗?发布你看过像我这样的新手所犯的常见错误,以及处理异常的一般建议,使我的应用程序更加稳定和健壮.
我目前正在努力解决的主要问题是:
感谢所有建议!
Joh*_*udy 79
再多几点......
您绝对应该有一个集中的异常处理策略.这可以像Main()在try/catch中包装一样简单,快速向用户发送优雅的错误消息.这是"最后的手段"异常处理程序.
如果可行,抢先检查总是正确的,但并不总是完美的.例如,在检查文件存在的代码和打开文件的下一行之间,文件可能已被删除或其他一些问题可能妨碍您访问.你仍然需要在那个世界中尝试/ catch/finally.根据需要同时使用抢先检查和try/catch/finally.
永远不要"吞下"异常,除非在绝对正确记录的情况下,确定抛出的异常是适合居住的.这几乎不会是这种情况.(如果是,请确保您只吞了特定的异常类-不要永远吞咽System.Exception.)
在构建库(由您的应用程序使用)时,不要吞下异常,也不要害怕异常冒泡.除非你有一些有用的东西要添加,否则不要重新投掷.不要(在C#中)这样做:
throw ex;
Run Code Online (Sandbox Code Playgroud)
因为你将擦除调用堆栈.如果必须重新抛出(有时需要,例如使用企业库的异常处理块时),请使用以下命令:
throw;
Run Code Online (Sandbox Code Playgroud)
在一天结束时,正在运行的应用程序抛出的绝大多数异常都应该暴露在某处.它们不应该暴露给最终用户(因为它们通常包含专有或其他有价值的数据),而是通常记录,并通知管理员异常.可以向用户呈现通用对话框,可以带有参考编号,以使事情变得简单.
.NET中的异常处理更多的是艺术而非科学.每个人都会在这里分享他们的最爱.这些只是我从第1天开始使用.NET获得的一些技巧,这些技巧不止一次地保存了我的培根.你的旅费可能会改变.
Mic*_*cah 62
这里有一篇优秀的代码CodeProject文章.以下是一些亮点:
小智 15
请注意,Windows窗体具有自己的异常处理机制.如果单击表单中的按钮并且其处理程序抛出未在处理程序中捕获的异常,则Windows窗体将显示其自己的"未处理的异常"对话框.
为了防止显示未处理的异常对话框并捕获此类异常以进行日志记录和/或提供您自己的错误对话框,您可以在Main()方法中调用Application.Run()之前附加到Application.ThreadException事件.
Rob*_*ney 14
到目前为止,这里发布的所有建议都很好,值得注意.
我想要扩展的一件事是你的问题"与先前测试磁盘上的文件是否存在等问题相比,处理可能抛出的异常会产生性能损失吗?"
天真的经验法则是"尝试/捕获块很昂贵".事实并非如此.尝试并不昂贵.这是捕获,系统必须创建一个Exception对象并使用堆栈跟踪加载它,这是昂贵的.在很多情况下,异常足够特殊,将代码包装在try/catch块中完全没问题.
例如,如果您正在填充字典,则:
try
{
dict.Add(key, value);
}
catch(KeyException)
{
}
Run Code Online (Sandbox Code Playgroud)
通常比这样做更快:
if (!dict.ContainsKey(key))
{
dict.Add(key, value);
}
Run Code Online (Sandbox Code Playgroud)
对于您要添加的每个项目,因为只有在添加重复键时才会抛出异常.(LINQ聚合查询执行此操作.)
在你给出的例子中,我几乎不假思索地使用try/catch.首先,只是因为当你检查它时文件存在并不意味着它在你打开它时会存在,所以你应该真正处理异常.
其次,我认为更重要的是,除非你的a)你的流程打开了成千上万的文件,b)它试图打开不存在的文件的几率并不是很低,创建异常的性能不是你的'我会注意到的.一般来说,当您的程序尝试打开文件时,它只会尝试打开一个文件.在这种情况下,编写更安全的代码几乎肯定会比编写最快的代码更好.
以下是我遵循的一些指导原则
快速失败:这是一个异常生成指南,对于您所做的每个假设以及您进入函数的每个参数都要进行检查,以确保您从正确的数据开始并假设您'制作是正确的.典型的检查包括,参数not null,预期范围内的参数等.
当重新抛出保留堆栈跟踪时 - 这简单地转换为在重新抛出时使用throw而不是throw new Exception().或者,如果您认为可以添加更多信息,则将原始异常包装为内部异常.但是如果你只是为了记录它而捕获异常,那么肯定会使用throw;
不要捕获你无法处理的异常,所以不要担心像OutOfMemoryException这样的事情,因为如果它们发生,你将无法做任何事情.
挂钩全局异常处理程序并确保记录尽可能多的信息.对于winforms挂钩appdomain和线程未处理的异常事件.
只有在分析代码并发现它导致性能瓶颈时才应考虑性能,默认情况下优化可读性和设计.所以关于文件存在检查的原始问题,我会说这取决于,如果你可以对文件不存在做一些事情,那么是这样检查否则如果你要做的就是抛出异常,如果文件是不是那里我没有看到这一点.
肯定有些时候需要空的catch块,我认为那些说不然的人还没有处理过几个版本的代码库.但是应该对它们进行评论和审查,以确保它们真的需要它们.最典型的例子是开发人员使用try/catch将字符串转换为整数而不是使用ParseInt().
如果您希望代码的调用者能够处理错误条件,那么创建自定义异常,详细说明非特定情况,并提供相关信息.否则只要尽可能坚持内置的异常类型.
您可以捕获 ThreadException 事件。
在解决方案资源管理器中选择一个 Windows 应用程序项目。
双击打开生成的 Program.cs 文件。
将以下代码行添加到代码文件的顶部:
using System.Threading;
Run Code Online (Sandbox Code Playgroud)在 Main() 方法中,添加以下内容作为该方法的第一行:
Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
Run Code Online (Sandbox Code Playgroud)在 Main() 方法下面添加以下内容:
static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
{
// Do logging or whatever here
Application.Exit();
}
Run Code Online (Sandbox Code Playgroud)添加代码以处理事件处理程序中未处理的异常。应用程序中其他任何地方未处理的任何异常均由上述代码处理。最常见的是,此代码应记录错误并向用户显示一条消息。
参考: https: //blogs.msmvps.com/deborahk/global-exception-handler-winforms/
例外是昂贵的,但也是必要的。您不需要将所有内容都包装在 try catch 中,但确实需要确保最终始终捕获异常。很大程度上取决于您的设计。
如果让异常上升也能起到同样的作用,则不要重新抛出。永远不要让错误被忽视。
例子:
void Main()
{
try {
DoStuff();
}
catch(Exception ex) {
LogStuff(ex.ToString());
}
void DoStuff() {
... Stuff ...
}
Run Code Online (Sandbox Code Playgroud)
如果 DoStuff 出了问题,你无论如何都会希望它退出。异常将被抛出到 main 中,您将在 ex 的堆栈跟踪中看到一系列事件。