C#:抛出自定义异常最佳实践
我已经阅读了一些关于C#异常处理实践的其他问题,但似乎没有人问我在寻找什么 如果我为一个特定类或一组类实现自己的自定义异常。应该使用内部异常将所有与这些类相关的错误封装到我的异常中,还是让它们失效 我想最好捕获所有异常,以便可以立即从我的源代码中识别异常。我仍然将原始异常作为内部异常传递。另一方面,我认为重新引用异常是多余的 例外情况:C#:抛出自定义异常最佳实践,c#,exception,custom-exceptions,C#,Exception,Custom Exceptions,我已经阅读了一些关于C#异常处理实践的其他问题,但似乎没有人问我在寻找什么 如果我为一个特定类或一组类实现自己的自定义异常。应该使用内部异常将所有与这些类相关的错误封装到我的异常中,还是让它们失效 我想最好捕获所有异常,以便可以立即从我的源代码中识别异常。我仍然将原始异常作为内部异常传递。另一方面,我认为重新引用异常是多余的 例外情况: class FooException : Exception { //... } class Foo { DoSomething(int pa
class FooException : Exception
{
//...
}
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);
}
}
}
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
}
}
选项1:Foo封装所有异常:
class FooException : Exception
{
//...
}
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);
}
}
}
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
}
}
选项2:Foo抛出特定的Foo异常,但允许其他异常通过:
class FooException : Exception
{
//...
}
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);
}
}
}
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
}
}
自定义异常的目的是向stacktrace提供详细的上下文信息,以帮助调试。选项1更好,因为没有它,如果异常发生在堆栈中的“较低”位置,则无法获得异常的“来源”。如果在Visual Studio中运行“异常”的代码段,则可以获得编写异常的良好实践模板 注意 选项1:您的
抛出新的FooException(“原因…”)代码>不会被捕获,因为它位于try/catch块之外
您应该只捕获要处理的异常
如果没有向异常添加任何附加数据,请使用抛出代码>因为它不会杀死堆栈。在选项2中,您仍然可以在catch内部进行一些处理,只需调用throw代码>以使用原始堆栈重新显示原始异常
选择2是最好的。我相信最佳实践是,当您计划对异常进行处理时,只捕获异常
在这种情况下,选项1只是用您自己的异常包装一个异常。它不会增加任何值,类的用户也不能仅仅捕获ArgumentException,例如,他们还需要捕获FooException,然后对内部异常进行解析。如果内部异常不是一个异常,他们可以做一些有用的事情,他们将需要重新调用。根据我对库的经验,出于以下几个原因,您应该将所有(您可以预期的)内容包装在footexception
中:
人们知道它来自于你的课堂,或者至少是他们对它们的使用。如果他们看到FileNotFoundException
他们可能正在到处寻找它。你在帮助他们缩小范围。(我现在意识到堆栈跟踪可以达到这个目的,所以您可以忽略这一点。)
您可以提供更多上下文。使用您自己的异常包装FNF,您可以说“我正试图为此目的加载此文件,但找不到它。这暗示了可能的正确解决方案
你的库可以正确地处理清理。如果你让异常冒泡,你就是在强迫用户清理。如果你正确地封装了你正在做的事情,那么他们就不知道如何处理这种情况
请记住只包装您可以预期的异常,如FileNotFound
。不要只包装Exception
,并期待最好的结果。看看这个
如果要重新抛出捕获的异常,请考虑使用throw
而不是throw ex
,因为这样原始堆栈跟踪会保持不变(行号等).在创建自定义异常时,我总是添加两个属性。一个是用户名或ID。我添加了一个DisplayMessage属性来携带要显示给用户的文本。然后,我使用Message属性来传递要记录在日志中的技术细节
我捕获数据访问层中的每一个错误,在这个级别上,我仍然可以捕获存储过程的名称和传递的参数值。或者内联SQL。可能是数据库名称或部分连接字符串(请不要凭据)。这些可能会出现在消息中,也可能出现在它们自己的新自定义DatabaseInfo属性中
对于网页,我使用相同的自定义异常。我将在Message属性中输入表单信息——用户在网页上的每个数据输入控件中输入的内容、正在编辑的项目的ID(客户、产品、员工等),以及用户在异常发生时采取的操作
因此,根据你的问题,我的策略是:只有在我可以对异常做一些事情时才能捕获。通常,我所能做的就是记录详细信息。因此,我只在这些详细信息可用的地方捕获,然后重新搜索,让异常冒泡到UI。我在自定义异常中保留原始异常。最重要的是在捕获异常时,代码需要知道的重要一点是系统的状态相对于它“应该”的状态(可能是因为出现了错误而抛出异常),异常不幸地完全从异常对象中丢失。如果LoadDocument方法中出现错误,则可能文档未成功加载,但至少有两种可能的系统状态:
系统状态可能好像从未尝试过加载。在这种情况下,如果应用程序可以在没有加载文档的情况下继续加载,则完全可以继续。
系统状态可能已充分损坏,最好的做法是将可以保存的内容保存到“恢复”文件中(避免用可能损坏的数据替换用户的好文件)并关闭。
显然,在这两个极端之间往往会有其他可能的状态。我建议,人们应该努力制定一个明确表明状态1存在的自定义例外,如果可预见但不可避免的情况可能导致状态1存在,则可能有一个针对状态2的例外。任何发生并将导致状态1的例外都应该是wra加入一个例外