C#:抛出自定义exception的最佳实践

我已经阅读了一些关于C#exception处理实践的其他问题,但似乎没有人问我在找什么。

如果我为特定的类或一组类实现自己的自定义exception。 应该把与这些类有关的所有错误封装到使用内部exception的exception中,还是让它们通过?

我在想,最好能够抓住所有exception,以便能够立即从我的源头识别exception。 我仍然把最初的exception作为一个内部的exception。 另一方面,我认为重新抛出exception是多余的。

例外:

class FooException : Exception { //... } 

select1:Foo囊括所有例外:

 class Foo { DoSomething(int param) { try { if (/*Something Bad*/) { //violates business logic etc... throw new FooException("Reason..."); } //... //something that might throw an exception } catch (FooException ex) { throw; } catch (Exception ex) { throw new FooException("Inner Exception", ex); } } } 

选项2:Foo抛出特定的FooExceptions,但允许其他exception通过:

 class Foo { DoSomething(int param) { if (/*Something Bad*/) { //violates business logic etc... throw new FooException("Reason..."); } //... //something that might throw an exception and not caught } } 

根据我对库的经验,你应该把所有的东西(你可以预见到的)都包含在FooException ,原因如下:

  1. 人们知道它来自你的课堂,或者至less是它们的使用。 如果他们看到FileNotFoundException他们可能正在寻找它。 你正在帮助他们缩小范围。 (我现在意识到堆栈跟踪服务于此目的,所以也许你可以忽略这一点。)

  2. 你可以提供更多的上下文。 用自己的例外包装一个FNF,你可以说:“我试图加载这个文件,并找不到它,这提示了可能的正确解决scheme。

  3. 您的库可以正确处理清理。 如果你让例外的气泡,你迫使用户清理。 如果你正确的封装了你正在做的事情,那么他们不知道如何处理这种情况!

请记住只包装可以预期的例外,如FileNotFound 。 不要只包装Exception并希望最好的。

看看这个MSDN最佳做法 。

考虑使用throw而不是throw ex如果你想重新抛出exception,因为这样原始的栈跟踪保持不变(行号等)。

创build自定义exception时,我总是添加一些属性。 一个是用户名或ID。 我添加一个DisplayMessage属性来携带要显示给用户的文本。 然后,我使用Message属性传递要logging在日志中的技术细节。

我捕获数据访问层中的每一个错误,我仍然可以捕获存储过程的名称和参数的值。 或者内联SQL。 也许数据库名称或部分连接string(请不要凭据)。 这些可能会在消息或在自己的新的自定义DatabaseInfo属性。

对于网页,我使用相同的自定义exception。 我将在Message属性中input表单信息 – 用户在网页上的每个数据input控件中input的内容,被编辑的项目的ID(客户,产品,员工等等)以及用户的操作当发生exception时正在采取。

所以,根据你的问题,我的策略是:只有当我能对exception做些什么的时候, 很多时候,我只能logging细节。 所以,我只是想知道这些细节是否可用,然后重新抛出,让exception冒泡到UI。 我在自定义exception中保留原始exception。

自定义exception的目的是向堆栈跟踪提供详细的上下文信息,以帮助进行debugging。 选项1更好,因为没有它,如果堆栈中出现“较低”,则不会得到exception的“来源”。

如果您在Visual Studio中运行“Exception”的代码段,则会有一个很好的例外编写模板。

注意选项1: throw new FooException("Reason..."); 将不会被捕获,因为它在try / catch块之外

  1. 你应该只捕捉你想处理的exception。
  2. 如果你没有添加任何额外的数据的exception比使用throw; 因为它不会杀死你的堆栈。 在选项2中,你仍然可以在catch中做一些处理,只是叫throw; 重新抛出原始堆栈的原始exception。

捕获一个exception的时候,最重要的事情是捕获exception,而不幸的是,这个exception完全从Exception对象中丢失,是系统相对于它“应该”的状态(可能是因为出现了错误而抛出的exception)。 如果在LoadDocument方法中发生错误,大概文档没有成功载入,但是至less有两种可能的系统状态:

  1. 系统状态可能好像负载从未尝试过。 在这种情况下,如果应用程序没有装入的文档就可以继续执行,那将是完全正确的。
  2. 系统状态可能已被充分破坏,最好的行动方式是保存可以保存到“恢复”文件(避免用可能损坏的数据replace用户的好文件)并closures。

显然在这两个极端之间经常会有其他可能的状态。 我build议人们应该努力制定一个自定义的例外,明确指出#1状态的存在,如果可预见但不可避免的情况可能导致#2可能的情况。 任何发生并将导致状态#1的exception应该被包装在指示状态#1的exception对象中。 如果exception可能以系统状态可能受到影响的方式发生,则应将其封装为#2或允许渗透。

选项2是最好的。 我相信最好的做法是只有当你打算做一些例外情况时才会发现exception情况。

在这种情况下,选项1只是用自己的例外包装exception。 它不增加任何价值,你的类的用户不能再捕获ArgumentException,例如,他们也需要捕获你的FooException,然后parsing内部exception。 如果内部exception不是一个例外,他们能够做一些有用的事情,他们将需要重新抛出。