C#:抛出自定义异常最佳实践
我已经阅读了一些有关 C# 异常处理实践的其他问题,但似乎没有一个问题询问我在寻找什么。
如果我为特定类或一组类实现自己的自定义异常。是否应该使用内部异常将与这些类相关的所有错误封装到我的异常中,还是应该让它们失败?
我想最好捕获所有异常,以便可以立即从我的源中识别异常。我仍然将原始异常作为内部异常传递。另一方面,我认为重新抛出异常是多余的。
异常:
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 抛出特定的 FooException,但允许其他异常失败:
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
}
}
I have read a few of the other questions regarding C# Exception Handling Practices but none seem to ask what I am looking for.
If I implement my own custom Exception for a particular class or set of classes. Should all errors that relate to those classes be encapsulated into my exception using inner exception or should I let them fall through?
I was thinking it would be better to catch all exceptions so that the exception can be immediately recognized from my source. I am still passing the original exception as an inner exception. On the other hand, I was thinking it would be redundant to rethrow the exception.
Exception:
class FooException : Exception
{
//...
}
Option 1: Foo encasulates all Exceptions:
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);
}
}
}
Option 2: Foo throws specific FooExceptions but allows other Exceptions to fall through:
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
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
自定义异常的目的是向堆栈跟踪提供详细的上下文信息以帮助调试。选项 1 更好,因为如果没有它,如果异常发生在堆栈中的“较低层”,您将无法获得异常的“起源”。
The purpose of custom exceptions is to provide detailed, contextual information to the stacktrace to aid in debugging. Option 1 is better because without it, you don't get the "origin" of the exception if it occurred "lower" in the stack.
注意
选项 1:您的
throw new FooException("Reason...");
不会被捕获,因为它位于 try / catch 块之外throw;
,因为它不会杀死您的堆栈。在选项 2 中,您仍然可以在 catch 内部进行一些处理,然后调用 throw; 来使用原始堆栈重新抛出原始异常。Note
Option 1: your
throw new FooException("Reason...");
won't be caught as it's outside try / catch blockthrow;
as it won't kill your stack. In Option 2 you still might do some processing inside catch and just callthrow;
to rethrow original exception with original stack.如果您在 Visual Studio 中运行“异常”的代码片段,您就有了一个良好的异常编写实践模板。
if you run the code snippet for 'Exception' in Visual Studio you have a template of a good practice exception writing.
代码在捕获异常时需要知道的最重要的事情是系统相对于它“应该”的状态的状态(不幸的是,异常对象完全缺失了这一点)(大概是因为出现问题而抛出异常)。如果 LoadDocument 方法中发生错误,则可能是文档未成功加载,但至少有两种可能的系统状态:
显然,在这些极端之间通常还存在其他可能的状态。我建议人们应该努力拥有一个自定义异常,明确表明状态#1存在,如果可预见但不可避免的情况可能导致状态#2存在,则可能有一个自定义异常。发生并导致状态 #1 的任何异常都应该包装在指示状态 #1 的异常对象中。如果异常以系统状态可能受到损害的方式发生,则应将它们包装为 #2 或允许渗透。
The most important thing for code to know when catching an exception, which is unfortunately completely missing from the Exception object, is the state of the system relative to what it "should" be (presumably the exception was thrown because there was something wrong). If an error occurs in a LoadDocument method, presumably the document didn't load successfully, but there are at least two possible system states:
Obviously there will often be other possible states between those extremes. I would suggest that one should endeavor to have a custom exception which explicitly indicates that state #1 exists, and possibly one for #2 if foreseeable but unavoidable circumstances may cause it. Any exceptions which occur and will result in state #1 should be wrapped in an exception object indicating state #1. If exceptions can occur in such a fashion that the system state might be compromised, they should either be wrapped as #2 or allowed to percolate.
选项 2 是最好的。我相信最佳实践是仅当您计划对异常执行某些操作时才捕获异常。
在这种情况下,选项 1 只是用您自己的异常包装异常。它没有增加任何价值,并且您的类的用户不能再仅仅捕获 ArgumentException,例如,他们还需要捕获您的 FooException 然后对内部异常进行解析。如果内部异常不是异常,他们能够做一些有用的事情,但他们需要重新抛出。
Option 2 is best. I believe best practice is to only catch exceptions when you plan to do something with the exception.
In this case, Option 1 just is wrapping an exception with your own exception. It adds no value and users of your class can no longer just catch ArgumentException, for example, they also need to catch your FooException then do parsing on the inner exception. If the inner exception is not an exception they are able to do something useful with they will need to rethrow.
根据我使用库的经验,您应该将所有内容(您可以预见的)包装在
FooException
中,原因如下:人们知道它来自您的类,或者至少是他们对他们。如果他们看到
FileNotFoundException
,他们可能会四处寻找。你正在帮助他们缩小范围。 (我现在意识到堆栈跟踪就是为了这个目的,所以也许你可以忽略这一点。)您可以提供更多上下文。用您自己的例外包装 FNF,您可以说“我试图加载此文件用于此目的,但找不到它。这暗示了可能的正确解决方案。
您的库可以正确处理清理。如果你让异常冒泡,你就是在强迫用户清理。如果您正确地封装了您正在做的事情,那么他们不知道如何处理这种情况!
请记住仅包装您可以预见的异常,例如
FileNotFound
。不要只是包装Exception
并希望得到最好的结果。Based on my experience with libraries, you should wrap everything (that you can anticipate) in a
FooException
for a few reasons:People know it came from your classes, or at least, their usage of them. If they see
FileNotFoundException
they may be looking all over for it. You're helping them narrow it down. (I realize now that the stack trace serves this purpose, so maybe you can ignore this point.)You can provide more context. Wrapping an FNF with your own exception, you can say "I was trying to load this file for this purpose, and couldn't find it. This hints at possible correct solutions.
Your library can handle cleanup correctly. If you let the exception bubble, you're forcing the user to clean up. If you've correctly encapsulated what you were doing, then they have no clue how to handle the situation!
Remember to only wrap the exceptions you can anticipate, like
FileNotFound
. Don't just wrapException
and hope for the best.看看这个 MSDN 最佳实践。
如果您想重新抛出捕获的异常,请考虑使用
throw 而不是
throw ex ,因为这样原始的堆栈跟踪会被保留(行号等)。
Have a look at this MSDN-best-practises.
Consider to use
throw
instead ofthrow ex
if you want to re-throw caught exceptions, because on this way the original stacktrace keeps preserved(line numbers etc.).创建自定义异常时,我总是添加几个属性。一是用户名或ID。我添加了一个 DisplayMessage 属性来携带要显示给用户的文本。然后,我使用 Message 属性来传达要记录在日志中的技术细节。
我捕获数据访问层中的每个错误,在该级别上我仍然可以捕获存储过程的名称和传递的参数值。或者内联 SQL。也许是数据库名称或部分连接字符串(请不要提供凭据)。这些可能会出现在 Message 中或位于它们自己的新自定义 DatabaseInfo 属性中。
对于网页,我使用相同的自定义异常。我将在 Message 属性中放入表单信息 - 用户在网页上的每个数据输入控件中输入的内容、正在编辑的项目的 ID(客户、产品、员工等)以及用户的操作异常发生时正在采取。
因此,根据您的问题,我的策略是:仅在我可以对异常采取措施时捕获。很多时候,我所能做的就是记录细节。因此,我仅在这些详细信息可用时捕获,然后重新抛出以使异常冒泡到 UI。我在自定义异常中保留了原始异常。
I always add a couple of properties when creating a custom exception. One is user name or ID. I add a DisplayMessage property to carry text to be displayed to the user. Then, I use the Message property to convey technical details to be recorded in the log.
I catch every error in the Data Access Layer at a level where I can still capture the name of the stored procedure and the values of the parameters passed. Or the inline SQL. Maybe the database name or partial connection string (no credentials, please). Those may go in Message or in their own new custom DatabaseInfo property.
For web pages, I use the same custom exception. I'll put in the Message property the form information -- what the user had entered into every data entry control on the web page, the ID of the item being edited (customer, product, employee, whatever), and the action the user was taking when the exception occurred.
So, my strategy as per your question is: only catch when I can do something about the exception. And quite often, all I can do is log the details. So, I only catch at the point where those details are available, and then rethrow to let the exception bubble up to the UI. And I retain the original exception in my custom exception.