C# Windows 窗体应用程序中异常处理的最佳实践?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/183589/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Best Practice for Exception Handling in a Windows Forms Application?
提问by Jon Artus
I'm currently in the process of writing my first Windows Forms application. I've read a few C# books now so I've got a relatively good understanding of what language features C# has to deal with exceptions. They're all quite theoretical however so what I haven't got yet is a feel for how to translate the basic concepts into a good exception-handling model in my application.
我目前正在编写我的第一个 Windows 窗体应用程序。我现在已经阅读了一些 C# 书籍,因此我对 C# 必须处理异常的语言特性有了比较好的了解。然而,它们都非常理论化,所以我还没有了解如何在我的应用程序中将基本概念转换为良好的异常处理模型。
Would anyone like to share any pearls of wisdom on the subject? Post any common mistakes you've seen newbies like myself make, and any general advice on handling exceptions in a way that will my application more stable and robust.
有人愿意分享关于这个主题的任何智慧珍珠吗?发布您看到像我这样的新手所犯的任何常见错误,以及任何关于以一种使我的应用程序更稳定和健壮的方式处理异常的一般建议。
The main things I'm currently trying to work out are:
我目前正在努力解决的主要问题是:
- When should I re-throw an exception?
- Should I try to have a central error-handling mechanism of some kind?
- Do handling exceptions which might be thrown have a performance hit compared with pre-emptively testing things like whether a file on disk exists?
- Should all executable code be enclosed in try-catch-finally blocks?
- Are there any times when an empty catch block might be acceptable?
- 我什么时候应该重新抛出异常?
- 我应该尝试拥有某种中央错误处理机制吗?
- 与预先测试磁盘上的文件是否存在相比,处理可能抛出的异常是否会影响性能?
- 所有可执行代码都应该包含在 try-catch-finally 块中吗?
- 是否有时可以接受空的 catch 块?
All advice gratefully received!
非常感谢所有建议!
回答by Echostorm
Exceptions are expensive but necessary. You don't need to wrap everything in a try catch but you do need to ensure that exceptions are always caught eventually. Much of it will depend on your design.
例外是昂贵的,但也是必要的。您不需要将所有内容都包装在 try catch 中,但您确实需要确保最终始终捕获异常。其中大部分将取决于您的设计。
Don't re-throw if letting the exception rise will do just as well. Never let errors pass unnoticed.
如果让异常上升也一样好,请不要重新抛出。永远不要让错误被忽视。
example:
例子:
void Main()
{
try {
DoStuff();
}
catch(Exception ex) {
LogStuff(ex.ToString());
}
void DoStuff() {
... Stuff ...
}
If DoStuff goes wrong you'll want it to bail anyway. The exception will get thrown up to main and you'll see the train of events in the stack trace of ex.
如果 DoStuff 出错,您无论如何都会希望它保释。异常将被抛出到 main,您将在 ex 的堆栈跟踪中看到事件序列。
回答by itsmatt
I like the philosophy of not catching anything I don't intend on handling, whatever handling means in my particular context.
我喜欢不捕捉任何我不打算处理的东西的哲学,无论处理在我的特定上下文中意味着什么。
I hate it when I see code such as:
当我看到诸如以下代码时,我讨厌它:
try
{
// some stuff is done here
}
catch
{
}
I have seen this from time to time and it is quite difficult to find problems when someone 'eats' the exceptions. A coworker I had does this and it tends to end up being a contributor to a steady stream of issues.
我不时看到这种情况,当有人“吃掉”例外时很难发现问题。我有一个同事这样做,它往往最终成为源源不断的问题的贡献者。
I re-throw if there is something that my particular class needs to do in response to an exception but the problem needs to be bubbled out to however called the method where it happened.
如果我的特定类需要做一些事情来响应异常,我会重新抛出,但问题需要冒泡出来,无论如何调用它发生的方法。
I think code should be written proactively and that exceptions should be for exceptional situations, not to avoid testing for conditions.
我认为应该主动编写代码,异常应该用于特殊情况,而不是避免测试条件。
回答by pezi_pink_squirrel
I'm just on my way out but will give you a brief run down on where to use exception handling. I will attempt to address your other points when I return :)
我刚刚离开,但会简要介绍在何处使用异常处理。当我回来时,我会尝试解决您的其他问题:)
- Explicitly check for all known error conditions*
- Add a try/catch around the code if your are unsure if you were able to handle all cases
- Add a try/catch around the code if the .NET interface you are calling throws an exception
- Add a try/catch around the code if it crosses a complexity threshold for you
- Add a try/catch around the code if for a sanity check: You are asserting THIS SHOULD NEVER HAPPEN
- As a general rule, I do not use exceptions as a replacement for return codes. This is fine for .NET, but not me. I do have exceptions (hehe) to this rule though, it depends on the architecture of the application your are working on as well.
- 明确检查所有已知错误情况*
- 如果您不确定是否能够处理所有情况,请在代码周围添加 try/catch
- 如果您正在调用的 .NET 接口引发异常,则在代码周围添加 try/catch
- 如果代码超过了复杂性阈值,则在代码周围添加 try/catch
- 如果进行健全性检查,请在代码周围添加 try/catch:您断言这应该永远不会发生
- 作为一般规则,我不使用异常来代替返回码。这适用于 .NET,但不适用于我。不过,我确实有此规则的例外(呵呵),这也取决于您正在处理的应用程序的体系结构。
*Within reason. There is no need to check to see if say a cosmic ray hit your data causing a couple bits to get flipped. Understanding what is "reasonable" is an acquired skill for an engineer. It's hard to quantify, yet easy to intuit. That is, I can easily explain why I use a try/catch in any particular instance, yet I am hard pressed to imbue another with this same knowledge.
*在合理范围内。无需检查是否说宇宙射线击中您的数据导致一些位被翻转。了解什么是“合理的”是工程师的一项后天技能。很难量化,但很容易凭直觉。也就是说,我可以很容易地解释为什么我在任何特定的情况下使用 try/catch,但我很难将同样的知识灌输给另一个人。
I for one tend to steer away from heavily exception based architectures. try/catch doesn't have a performance hit as such, the hit comes in when the exception is thrown and the code might have to walk up several levels of the call stack before something handles it.
我倾向于避开严重基于异常的架构。try/catch 本身不会对性能造成影响,当抛出异常时就会出现这种情况,并且代码在处理它之前可能必须走上调用堆栈的几个级别。
回答by sebagomez
When should I re-throw an exception?
我什么时候应该重新抛出异常?
Everywhere, but end user methods... like button click handlers
无处不在,但最终用户方法......比如按钮点击处理程序
Should I try to have a central error-handling mechanism of some kind?
我应该尝试拥有某种中央错误处理机制吗?
I write a log file... pretty easy for a WinForm app
我写了一个日志文件......对于 WinForm 应用程序来说非常简单
Do handling exceptions which might be thrown have a performance hit compared with pre-emptively testing things like whether a file on disk exists?
与预先测试磁盘上的文件是否存在相比,处理可能抛出的异常是否会影响性能?
I'm not sure about this, but I believe it is a good practice to thow exceptions... I mean you can ask whether a file exists and if it doesn't throw a FileNotFoundException
我不确定这一点,但我相信抛出异常是一个很好的做法......我的意思是你可以询问文件是否存在以及它是否不抛出 FileNotFoundException
Should all executable code be enclosed in try-catch-finally blocks?
所有可执行代码都应该包含在 try-catch-finally 块中吗?
yeap
是的
Are there any times when an empty catch block might be acceptable?
是否有时可以接受空的 catch 块?
Yes, let's say you want to show a date, but you have no clue how that date was stores (dd/mm/yyyy, mm/dd/yyyy, etc) you try tp parse it but if it fails just keep going... if it is irrelevant to you... I would say yes, there is
是的,假设您想显示一个日期,但您不知道该日期是如何存储的(dd/mm/yyyy、mm/dd/yyyy 等),您尝试 tp 解析它,但如果它失败,请继续.. .如果它与你无关......我会说是的,有
回答by BKimmel
The one thing I learned very quickly was to enclose absolutely everychunk of code that interacts with anythingoutside the flow of my program (i.e. File System, Database Calls, User Input) with try-catch blocks. Try-catch can incur a performance hit, but usually in these places in your code it won't be noticeable and it will pay for itself with safety.
I have used empty catch-blocks in places where the user might do something that isn't really "incorrect", but it can throw an exception...an example that comes to mind is in a GridView if the user DoubleCLicks the gray placeholder cell on the top-left it will fire the CellDoubleClick event, but the cell doesn't belong to a row. In that case, you dontreally need to post a message but if you don't catch it it will throw an unhandled exception error to the user.
我很快学到的一件事就是用 try-catch 块将与程序流程之外的任何事物(即文件系统、数据库调用、用户输入)交互的每一块代码都包含在内。Try-catch 可能会导致性能下降,但通常在代码中的这些地方,它不会引起注意,并且会以安全的方式收回成本。我在用户可能会做一些并非真正“不正确”的事情的地方使用了空的捕获块,但它可能会抛出异常......如果用户双击灰色占位符,我想到的一个例子是在 GridView 中左上角的单元格将触发 CellDoubleClick 事件,但该单元格不属于一行。在这种情况下,你不
确实需要发布一条消息,但如果您没有抓住它,它将向用户抛出一个未处理的异常错误。
回答by Micah
There is an excellent code CodeProject article here. Here are a couple of highlights:
这里有一篇优秀的代码CodeProject 文章。这里有几个亮点:
- Plan for the worst*
- Check it early
- Don't trust external data
- The only reliable devices are: the video, the mouse and keyboard.
- Writes can fail, too
- Code Safely
- Don't throw new Exception()
- Don't put important exception information on the Message field
- Put a single catch (Exception ex) per thread
- Generic Exceptions caught should be published
- Log Exception.ToString(); never log only Exception.Message!
- Don't catch (Exception) more than once per thread
- Don't ever swallow exceptions
- Cleanup code should be put in finally blocks
- Use "using" everywhere
- Don't return special values on error conditions
- Don't use exceptions to indicate absence of a resource
- Don't use exception handling as means of returning information from a method
- Use exceptions for errors that should not be ignored
- Don't clear the stack trace when re-throwing an exception
- Avoid changing exceptions without adding semantic value
- Exceptions should be marked [Serializable]
- When in doubt, don't Assert, throw an Exception
- Each exception class should have at least the three original constructors
- Be careful when using the AppDomain.UnhandledException event
- Don't reinvent the wheel
- Don't use Unstructured Error Handling (VB.Net)
- 做最坏的打算*
- 早点检查
- 不要相信外部数据
- 唯一可靠的设备是:视频、鼠标和键盘。
- 写入也可能失败
- 安全编码
- 不要抛出新的 Exception()
- 不要把重要的异常信息放在Message字段
- 每个线程放一个 catch (Exception ex)
- 应发布捕获的通用异常
- 记录异常。ToString(); 从不只记录 Exception.Message!
- 每个线程不要多次捕获(异常)
- 永远不要吞下异常
- 清理代码应该放在 finally 块中
- 到处使用“使用”
- 不要在错误条件下返回特殊值
- 不要使用异常来指示资源的缺失
- 不要使用异常处理作为从方法返回信息的手段
- 对不应忽略的错误使用异常
- 重新抛出异常时不要清除堆栈跟踪
- 避免在不添加语义值的情况下更改异常
- 异常应标记为 [Serializable]
- 有疑问时,不要断言,抛出异常
- 每个异常类至少应该有三个原始构造函数
- 使用 AppDomain.UnhandledException 事件时要小心
- 不要重新发明轮子
- 不要使用非结构化错误处理 (VB.Net)
回答by John Rudy
A few more bits ...
还有几位...
You absolutely should have a centralized exception handling policy in place. This can be as simple as wrapping Main()
in a try/catch, failing fast with a graceful error message to the user. This is the "last resort" exception handler.
您绝对应该有一个集中的异常处理策略。这可以像包装Main()
在 try/catch 中一样简单,快速失败并向用户显示优雅的错误消息。这是“最后的手段”异常处理程序。
Preemptive checks are always correct if feasible, but not always perfect. For example, between the code where you check for a file's existence and the next line where you open it, the file could have been deleted or some other issue may impede your access. You still need try/catch/finally in that world. Use both the preemptive check and the try/catch/finally as appropriate.
如果可行,抢先检查总是正确的,但并不总是完美的。例如,在检查文件存在的代码和打开文件的下一行之间,文件可能已被删除或其他一些问题可能会阻碍您的访问。在那个世界中,您仍然需要 try/catch/finally。根据需要同时使用抢先检查和 try/catch/finally。
Never "swallow" an exception, except in the most well-documented cases when you are absolutely, positively sure that the exception being thrown is livable. This will almost never be the case. (And if it is, make sure you're swallowing only the specificexception class -- don't everswallow System.Exception
.)
永远不要“吞下”异常,除非在最有据可查的情况下,当您绝对、肯定地确定抛出的异常是可行的。这几乎永远不会发生。(如果是,请确保您只吞下特定的异常类——永远不要吞下System.Exception
。)
When building libraries (used by your app), do not swallow exceptions, and do not be afraid to let the exceptions bubble up. Do not re-throw unless you have something useful to add. Do not ever (in C#) do this:
在构建库(由您的应用程序使用)时,不要吞下异常,也不要害怕让异常冒泡。除非你有一些有用的东西要添加,否则不要重新抛出。永远不要(在 C# 中)这样做:
throw ex;
As you will erase the call stack. If you must re-throw (which is occasionally necessary, such as when using the Exception Handling Block of Enterprise Library), use the following:
因为您将擦除调用堆栈。如果您必须重新抛出(偶尔需要,例如在使用企业库的异常处理块时),请使用以下内容:
throw;
At the end of the day, the very vast majority of exceptions thrown by a running application should be exposed somewhere. They should not be exposed to end users (as they often contain proprietary or otherwise valuable data), but rather usually logged, with administrators notified of the exception. The user can be presented with a generic dialog box, maybe with a reference number, to keep things simple.
归根结底,正在运行的应用程序抛出的绝大多数异常都应该在某处公开。它们不应该暴露给最终用户(因为它们通常包含专有或其他有价值的数据),而是通常被记录下来,并通知管理员异常。用户可以看到一个通用对话框,可能带有一个参考编号,以保持简单。
Exception handling in .NET is more art than science. Everyone will have their favorites to share here. These are just a few of the tips I've picked up using .NET since day 1, techniques which have saved my bacon on more than one occasion. Your mileage may vary.
.NET 中的异常处理与其说是科学,不如说是艺术。每个人都会在这里分享他们的最爱。这些只是我从第一天开始使用 .NET 学到的一些技巧,这些技巧不止一次拯救了我的培根。你的旅费可能会改变。
回答by Sijin
Here are a few guidelines that I follow
以下是我遵循的一些准则
Fail-Fast: This is more of a exception generating guideline, For every assumption that you make and every parameter that you are getting into a function do a check to make sure that you're starting off with the right data and that the assumptions you're making are correct. Typical checks include, argument not null, argument in expected range etc.
When rethrowing preserve stack trace - This simply translates to using throw when rethrowing instead of throw new Exception(). Alternatively if you feel that you can add more information then wrap the original exception as an inner exception. But if you're catching an exception only to log it then definitely use throw;
Do not catch exceptions that you cannot handle, so don't worry about things like OutOfMemoryException because if they occur you won't be able to do much anyways.
Do hook global exception handlers and make sure to log as much information as possible. For winforms hook both the appdomain and thread unhandled exception events.
Performance should only be taken into consideration when you've analyzed the code and seen that it's causing a performance bottleneck, by default optimize for readability and design. So about your original question on the file existence check, I would say it depends, If you can do something about the file not being there, then yes do that check otherwise if all you're going to do is throw an exception if the file's not there then I don't see the point.
There are definitely times when empty catch blocks are required, I think people who say otherwise have not worked on codebases that have evolved over several releases. But they should be commented and reviewed to make sure that they're really needed. The most typical example is developers using try/catch to convert string to integer instead of using ParseInt().
If you expect the caller of your code to be able to handle error conditions then create custom exceptions that detail what the un excepected situation is and provide relevant information. Otherwise just stick to built-in exception types as much as possible.
快速失败:这更像是一个异常生成指南,对于您所做的每个假设和您进入函数的每个参数进行检查以确保您从正确的数据开始,并且假设您正在制作是正确的。典型的检查包括参数不为空、参数在预期范围内等。
重新抛出时保留堆栈跟踪 - 这简单地转换为重新抛出时使用 throw 而不是 throw new Exception()。或者,如果您觉得可以添加更多信息,则将原始异常包装为内部异常。但是,如果您只是为了记录异常而捕获异常,那么一定要使用 throw;
不要捕获您无法处理的异常,所以不要担心 OutOfMemoryException 之类的事情,因为如果它们发生,您无论如何都无法做很多事情。
挂钩全局异常处理程序并确保记录尽可能多的信息。对于 winforms 挂钩 appdomain 和线程未处理的异常事件。
只有当您分析代码并看到它导致性能瓶颈时,才应考虑性能,默认情况下优化可读性和设计。所以关于你关于文件存在检查的原始问题,我会说这取决于,如果你可以对文件不存在做一些事情,那么是的,否则如果你要做的就是抛出异常,如果文件不存在不在那里,那么我不明白这一点。
肯定有一些时候需要空的 catch 块,我认为那些说否则的人没有在经过多个版本演变的代码库上工作。但是应该对它们进行评论和审查,以确保它们真的被需要。最典型的例子是开发人员使用 try/catch 将字符串转换为整数,而不是使用 ParseInt()。
如果您希望代码的调用者能够处理错误情况,则创建自定义异常,详细说明意外情况并提供相关信息。否则,只需尽可能坚持内置异常类型。
回答by Brad8118
When re-throwing an exception the key word throw by it self. This will throw the caught exception and still will be able to use stack trace to see where it came from.
当重新抛出异常时,关键字由它自己抛出。这将抛出捕获的异常,并且仍然可以使用堆栈跟踪来查看它来自哪里。
Try
{
int a = 10 / 0;
}
catch(exception e){
//error logging
throw;
}
doing this will cause the stack trace to end in the catch statement. (avoid this)
这样做将导致堆栈跟踪以 catch 语句结束。(避免这种情况)
catch(Exception e)
// logging
throw e;
}
回答by Brad8118
The golden rule that have tried to stick to is handle the exception as close to the source as possible.
试图坚持的黄金法则是尽可能接近源头处理异常。
If you must re-throw an exception try to add to it, re-throwing a FileNotFoundException does not help much but throwing a ConfigurationFileNotFoundException will allow it to be captured and acted upon somewhere up the chain.
如果您必须重新抛出一个异常尝试添加到它,重新抛出一个 FileNotFoundException 没有多大帮助,但抛出一个 ConfigurationFileNotFoundException 将允许它被捕获并在链上的某个地方采取行动。
Another rule I try to follow is not to use try/catch as a form of program flow, so I do verify files/connections, ensure objects have been initiated, ect.. prior to using them. Try/catch should be for Exceptions, things you can not control.
我尝试遵循的另一条规则是不使用 try/catch 作为程序流程的一种形式,因此我会在使用它们之前验证文件/连接,确保对象已启动等。Try/catch 应该用于异常,您无法控制的事情。
As for an empty catch block, if you are doing anything of importance in the code that generated the exception you should re-throw the exception at a minimum. If there is no consequences of the code that threw the exception not running why did you write it in the first place.
至于一个空的 catch 块,如果你在产生异常的代码中做任何重要的事情,你应该至少重新抛出异常。如果抛出异常的代码没有运行的后果,你为什么首先编写它。