C#:抛出自定义异常最佳实践

时间:2011-01-21 16:30:38

标签: c# exception custom-exceptions

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

如果我为特定的类或类集实现自己的自定义Exception。是否应该使用内部异常将与这些类相关的所有错误封装到我的异常中,或者我应该让它们落空?

我认为捕获所有异常会更好,以便可以从我的源代码中立即识别异常。我仍然将原始异常作为内部异常传递。另一方面,我认为重新抛出异常是多余的。

例外:

class FooException : Exception
{
    //...
}

选项1: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但允许其他异常掉线:

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

8 个答案:

答案 0 :(得分:54)

根据我对图书馆的经验,您应该在FooException中包装所有内容(您可以预期),原因如下:

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

  2. 您可以提供更多背景信息。使用您自己的例外包装FNF,您可以说“我正在尝试为此目的加载此文件 ,但无法找到它。这暗示了可能的正确解决方案。

  3. 您的图书馆可以正确处理清理工作。如果你让异常气泡,你就强迫用户清理。如果你已经正确地封装了你正在做的事情,那么他们就不知道如何处理这种情况了!

  4. 请记住只包装您可以预料到的例外情况,例如FileNotFound。不要只包裹Exception并希望最好。

答案 1 :(得分:18)

看看这个MSDN-best-practises

如果要重新抛出捕获的异常,请考虑使用throw而不是throw ex,因为这样原始堆栈跟踪会保留(行号等)。

答案 2 :(得分:6)

创建自定义异常时,我总是添加几个属性。一个是用户名或ID。我添加了一个DisplayMessage属性来携带要显示给用户的文本。然后,我使用Message属性来传达要记录在日志中的技术细节。

我捕获数据访问层中的每个错误,我仍然可以捕获存储过程的名称和传递的参数值。或内联SQL。也许是数据库名称或部分连接字符串(请不要凭据)。这些可以在Message或他们自己的新自定义DatabaseInfo属性中。

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

所以,根据你的问题我的策略是:只有当我能对异常做些什么时才会抓住。通常,我所能做的就是记录细节。所以,我只抓住那些细节可用的点,然后重新抛出让异常泡到UI。我在自定义异常中保留了原始异常。

答案 3 :(得分:3)

自定义异常的目的是为堆栈跟踪提供详细的上下文信息以帮助调试。选项1更好,因为没有它,如果它在堆栈中出现“较低”,则不会获得异常的“起源”。

答案 4 :(得分:1)

如果在Visual Studio中运行“异常”的代码片段,则会有一个良好练习异常编写的模板。

答案 5 :(得分:1)

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

  1. 您应该只捕获要处理的异常。
  2. 如果您没有向异常添加任何其他数据而不是使用throw;,因为它不会杀死您的堆栈。在选项2中,您仍然可以在catch中进行一些处理,只需调用throw;以使用原始堆栈重新抛出原始异常。

答案 6 :(得分:1)

代码在捕获异常时最重要的一点是,异常对象完全遗漏了异常,系统的状态是相对于“应该”的异常(可能是异常被抛出,因为有一些东西错误)。如果LoadDocument方法中发生错误,可能是文档未成功加载,但至少有两种可能的系统状态:

  1. 系统状态可能好像从未尝试过加载。在这种情况下,如果应用程序在没有加载文档的情况下可以继续,那么应用程序将继续完全正确。
  2. 系统状态可能已经完全损坏,最好的做法是保存可以保存到“恢复”文件的内容(避免用可能损坏的数据替换用户的好文件)并关闭。

显然,在这些极端之间经常会有其他可能的状态。我建议人们应该努力拥有一个自定义异常,明确指出状态#1存在,如果可预见但可能会导致#2,可能会导致#2。发生并将导致状态#1的任何异常都应该包含在指示状态#1的异常对象中。如果异常可能以系统状态可能受到损害的方式发生,则它们应该被包装为#2或允许渗透。

答案 7 :(得分:0)

选项2是最好的。我认为最佳做法是仅在您计划对异常执行某些操作时捕获异常。

在这种情况下,选项1只是用您自己的异常包装异常。它没有添加任何值,并且您的类的用户不能再只捕获ArgumentException,例如,他们还需要捕获您的FooException然后解析内部异常。如果内部异常不是例外,他们可以做一些有用的事情,他们需要重新抛出。