119

我目前正在编写我的第一个 Windows 窗体应用程序。我现在已经阅读了几本 C# 书籍,因此我对 C# 必须处理异常的哪些语言特性有了一个相对较好的理解。它们都是非常理论化的,但是我还没有了解如何在我的应用程序中将基本概念转化为良好的异常处理模型。

有人愿意分享有关该主题的任何智慧珍珠吗?发布您看到像我这样的新手所犯的任何常见错误,以及有关以使我的应用程序更加稳定和健壮的方式处理异常的任何一般建议。

我目前正在努力解决的主要问题是:

  • 我什么时候应该重新抛出异常?
  • 我应该尝试使用某种中央错误处理机制吗?
  • 与先发制人地测试诸如磁盘上的文件是否存在之类的事情相比,处理可能引发的异常是否会影响性能?
  • 所有可执行代码都应该包含在 try-catch-finally 块中吗?
  • 是否有任何时候可以接受空的 catch 块?

感谢所有建议!

4

16 回答 16

83

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.

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.

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 specific exception class -- don't ever swallow 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:

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.

于 2008-10-08T16:33:12.360 回答
65

There is an excellent code CodeProject article here. Here are a couple of highlights:

  • 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)
于 2008-10-08T16:30:46.110 回答
16

请注意,Windows 窗体有它自己的异常处理机制。如果单击表单中的按钮并且其处理程序引发了处理程序中未捕获的异常,Windows 窗体将显示其自己的未处理异常对话框。

为了防止显示未处理的异常对话框并捕获此类异常以进行日志记录和/或提供您自己的错误对话框,您可以在 Main() 方法中调用 Application.Run() 之前附加到 Application.ThreadException 事件。

于 2009-04-24T20:08:32.980 回答
15

到目前为止,这里发布的所有建议都很好,值得关注。

我想扩展的一件事是您的问题“与先发制人地测试诸如磁盘上的文件是否存在之类的事情相比,处理可能引发的异常是否会影响性能?”

天真的经验法则是“try/catch 块很昂贵”。事实并非如此。尝试并不昂贵。关键是系统必须创建一个异常对象并使用堆栈跟踪加载它,这很昂贵。在很多情况下,异常足够异常,可以将代码包装在 try/catch 块中。

例如,如果您要填充字典,则:

try
{
   dict.Add(key, value);
}
catch(KeyException)
{
}

通常比这样做更快:

if (!dict.ContainsKey(key))
{
   dict.Add(key, value);
}

对于您添加的每个项目,因为只有在添加重复键时才会引发异常。(LINQ 聚合查询执行此操作。)

在您给出的示例中,我几乎不假思索地使用 try/catch。首先,仅仅因为文件在您检查时存在并不意味着当您打开它时它就会存在,所以无论如何您都应该真正处理异常。

其次,我认为更重要的是,除非您的 a)您的进程正在打开数千个文件,并且 b)它试图打开的文件不存在的可能性并不低,否则创建异常对性能的影响不是您的事情重新注意到。一般来说,当你的程序试图打开一个文件时,它只是试图打开一个文件。在这种情况下,编写更安全的代码几乎肯定会比编写尽可能快的代码更好。

于 2008-10-08T18:34:54.020 回答
9

Here are a few guidelines that I follow

  1. 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.

  2. 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;

  3. 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.

  4. 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.

  5. 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.

  6. 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().

  7. 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.

于 2008-10-08T16:36:19.093 回答
4

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.

于 2008-10-08T16:24:19.460 回答
4

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

  1. Explicitly check for all known error conditions*
  2. Add a try/catch around the code if your are unsure if you were able to handle all cases
  3. Add a try/catch around the code if the .NET interface you are calling throws an exception
  4. Add a try/catch around the code if it crosses a complexity threshold for you
  5. Add a try/catch around the code if for a sanity check: You are asserting THIS SHOULD NEVER HAPPEN
  6. 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.

*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.

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.

于 2008-10-08T16:25:22.907 回答
4

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.

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.

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.

于 2008-10-08T16:38:33.707 回答
3

您可以捕获 ThreadException 事件。

  1. 在解决方案资源管理器中选择一个 Windows 应用程序项目。

  2. 通过双击打开生成的 Program.cs 文件。

  3. 将以下代码行添加到代码文件的顶部:

    using System.Threading;
    
  4. 在 Main() 方法中,添加以下内容作为方法的第一行:

    Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
    
  5. 在 Main() 方法下面添加以下内容:

    static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
    {
        // Do logging or whatever here
        Application.Exit();
    }
    
  6. 添加代码以处理事件处理程序中未处理的异常。应用程序中其他任何地方未处理的任何异常都由上述代码处理。最常见的是,此代码应记录错误并向用户显示消息。

参考:https ://blogs.msmvps.com/deborahk/global-exception-handler-winforms/

于 2019-01-15T15:19:16.353 回答
2

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.

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.

于 2008-10-08T16:22:38.857 回答
1

根据我的经验,当我知道我将要创建异常时,我认为捕获异常是合适的。例如,当我在一个 Web 应用程序中并且我正在执行 Response.Redirect 时,我知道我会得到一个 System.ThreadAbortException。因为这是故意的,所以我只是抓住了特定类型的东西,然后吞下它。

try
{
/*Doing stuff that may cause an exception*/
Response.Redirect("http:\\www.somewhereelse.com");
}
catch (ThreadAbortException tex){/*Ignore*/}
catch (Exception ex){/*HandleException*/}
于 2009-02-05T21:27:57.637 回答
1

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

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

Should all executable code be enclosed in try-catch-finally blocks?

yeap

Are there any times when an empty catch block might be acceptable?

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

于 2008-10-08T16:25:27.227 回答
1

The one thing I learned very quickly was to enclose absolutely every chunk of code that interacts with anything outside 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 dont really need to post a message but if you don't catch it it will throw an unhandled exception error to the user.

于 2008-10-08T16:26:04.420 回答
1

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(Exception e)
// logging
throw e;
}
于 2008-10-08T16:38:02.243 回答
1

我非常同意以下规则:

  • 永远不要让错误被忽视。

原因是:

  • 当您第一次写下代码时,很可能您不会完全了解 3 方代码、.NET FCL 库或您的同事的最新贡献。实际上,在您充分了解每个异常可能性之前,您不能拒绝编写代码。所以
  • 我经常发现我使用 try/catch(Exception ex) 只是因为我想保护自己免受未知事物的侵害,而且,正如您所注意到的,我捕获了异常,而不是更具体的异常,例如 OutOfMemoryException 等。而且,我总是抛出异常被 ForceAssert.AlwaysAssert(false, ex.ToString() ) 弹出给我(或 QA);

ForceAssert.AlwaysAssert 是我个人的 Trace.Assert 方式,无论是否定义了 DEBUG/TRACE 宏。

开发周期可能是:我注意到丑陋的 Assert 对话框或其他人向我抱怨它,然后我回到代码并找出引发异常的原因并决定如何处理它。

通过这种方式,我可以在短时间内写下我的代码,并保护我免受未知领域的影响,但如果发生异常情况,我总是会注意到,这样系统就变得更安全了。

我知道你们中的许多人不会同意我的观点,因为开发人员应该知道他/她的代码的每一个细节,坦率地说,我在过去也是一个纯粹主义者。但现在我了解到,上述政策更加务实。

对于 WinForms 代码,我始终遵守的一条黄金法则是:

  • 始终尝试/捕获(异常)您的事件处理程序代码

这将保护您的 UI 始终可用。

对于性能损失,性能损失仅在代码达到 catch 时发生,在没有引发实际异常的情况下执行 try 代码没有显着影响。

异常应该很少发生,否则就不是异常。

于 2009-03-15T15:21:17.667 回答
-1

你必须考虑用户。应用程序崩溃是最后一次用户想要的东西。因此,任何可能失败的操作都应该在 ui 级别有一个 try catch 块。没有必要在每个方法中都使用 try catch,但每次用户做某事时,它必须能够处理一般异常。这绝不能让您免于检查所有内容以防止在第一种情况下出现异常,但是没有没有错误的复杂应用程序,并且操作系统很容易添加意外问题,因此您必须预测意外并确保用户是否想要使用一个操作不会因为应用程序崩溃而丢失数据。没有必要让您的应用程序崩溃,如果您捕获异常,它将永远不会处于不确定状态,并且用户总是因崩溃而感到不便。即使异常在最顶层,不崩溃意味着用户可以快速重现异常或至少记录错误消息,从而极大地帮助您解决问题。当然,不仅仅是收到一条简单的错误消息,然后只看到 Windows 错误对话框或类似的东西。

这就是为什么您绝不能自负并认为您的应用程序没有错误,这是不能保证的。包装一些有关适当代码的 try catch 块并显示错误消息/记录错误是一项非常小的工作。

作为用户,每当浏览器或办公应用程序或其他任何崩溃时,我肯定会非常生气。如果异常如此之高以至于应用程序无法继续,最好显示该消息并告诉用户该做什么(重新启动,修复一些操作系统设置,报告错误等),而不是简单地崩溃,仅此而已。

于 2017-03-14T11:51:00.537 回答