我在看关于可序列化DTO的文章C-数据传输对象。
这篇文章包括这段代码:
1 2 3 4 5 6 7 8 9 10 11
| public static string SerializeDTO (DTO dto ) {
try {
XmlSerializer xmlSer = new XmlSerializer (dto .GetType());
StringWriter sWriter = new StringWriter ();
xmlSer .Serialize(sWriter, dto );
return sWriter .ToString();
}
catch(Exception ex ) {
throw ex ;
}
} |
文章的其余部分看起来是理智和合理的(对一个noob来说),但那次尝试接球抛出了一个wtfeexception…这是否完全等同于根本不处理异常?
埃尔戈:
1 2 3 4 5 6
| public static string SerializeDTO (DTO dto ) {
XmlSerializer xmlSer = new XmlSerializer (dto .GetType());
StringWriter sWriter = new StringWriter ();
xmlSer .Serialize(sWriter, dto );
return sWriter .ToString();
} |
或者我是否遗漏了有关C中错误处理的一些基本信息?它和Java(减去异常)差不多,不是吗?…也就是说,它们都精炼C++。
堆栈溢出问题重新抛出参数减去catch和不做任何事情之间的区别?似乎支持我的观点,即尝试-接球-投掷是不允许的。
编辑:
只是为将来找到这个线索的人总结一下…
不
1 2 3 4 5 6
| try {
// Do stuff that might throw an exception
}
catch (Exception e) {
throw e; // This destroys the strack trace information!
} |
堆栈跟踪信息对于识别问题的根本原因至关重要!
做
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| try {
// Do stuff that might throw an exception
}
catch (SqlException e ) {
// Log it
if (e .ErrorCode != NO_ROW_ERROR ) { // filter out NoDataFound.
// Do special cleanup, like maybe closing the"dirty" database connection.
throw; // This preserves the stack trace
}
}
catch (IOException e ) {
// Log it
throw;
}
catch (Exception e ) {
// Log it
throw new DAOException ("Excrement occurred", e ); // wrapped & chained exceptions (just like java).
}
finally {
// Normal clean goes here (like closing open files).
} |
在特定的异常之前捕获更具体的异常(就像Java)。
参考文献:
- 很好的总结;包含最后一个块的额外要点。
- 我想补充一点,您可以使用"throw";将发送给方法的参数添加到"throw!"语句之前的E.数据收集中,这样更有用。
- @米奇·沃玛(兼兼职画家)。呵呵?你说的是处理Microshite Suckwell(据我所知,从2005年起)例外。我说的是一般的异常处理。是的,我从四年前发表这篇文章开始就学到了一些……但是,是的,我承认你有正确的观点,但我认为你错过了真正的观点;如果你明白我的意思?这个问题是关于C中的一般性异常处理,更具体地说是关于重新引发异常…各种各样的。Cool?
- 请考虑将问题中的编辑摘要部分移到其自己的答案。有关原因,请参见编辑问题外的自答和问题中嵌入的答案。
- 包装第三方模块时捕获并抛出新的异常似乎是明智的,因为它会在整个解决方案中消除对该模块的依赖。"通过C的自适应代码"中的一些例子很好地解释了这一点,我相信有很好的理由。然而,你的第一个例子并不是一个很好的例子。
- 有没有人注意到"大便发生"部分?这听起来像是大便的密码!
首先,文章中的代码做这件事的方式是邪恶的。throw ex会将异常中的调用堆栈重置到该throw语句所在的位置;丢失有关异常实际创建位置的信息。
第二,如果您只是这样捕获并重新抛出,我看不到任何附加值,那么上面的代码示例在不使用try-catch的情况下也会很好(或者,考虑到throw ex位,甚至更好)。
但是,在某些情况下,您可能希望捕获并重新引发异常。日志记录可能是其中之一:
1 2 3 4 5 6 7 8 9
| try
{
// code that may throw exceptions
}
catch(Exception ex)
{
// add error logging here
throw;
} |
- @Fredrick,仅供参考(尽管你可能知道),如果你不打算使用那个ex对象,那么就不需要实例化它了。
- @伊恩:我知道。在这种情况下,我建议捕获异常,以便在重新引发之前对其执行某些操作(如日志记录),然后我将需要异常实例。不过,这一点很有道理。
- @伊欧因:如果它没有被实例化,就很难记录下来。
- @弗雷德里克:我认为‘邪恶’有点强大。做throw ex可能不是很有用,但是这样做除了丢失调用堆栈信息之外没有负面的副作用。
- @迪沃:是的,有点强,同意。但我见过很多次这种错误的做法(我认为,这不是一次,在哪里是一个好主意),所以我想说清楚。
- 是的,我认为"邪恶"是正确的…考虑从大量代码中抛出空指针异常的情况。消息是普通的,没有堆栈跟踪,您只剩下"something was null somewhere"。当生产停滞时,情况就不好了;而且你没有时间或更少的时间去找出火焰的问题所在,并消除或纠正它…良好的异常处理是值得的。
- 对Java也是如此吗?抛出"vs.抛出ex"?
- 在日志代码引发异常的情况下会发生什么?
- @杰森,看这个问题。在爪哇中,EDCOX1×1不重启STACKTROW。
- @Corlettk我发现,通过设置vs来打破所有的异常,而不仅仅是未捕获的异常,可以部分缓解问题。
- 当然,清除调用堆栈可能正是最初的程序员想要的。可能不是,但可能是。
- "清理"编译器警告抱怨未使用的ex变量的最佳方法?
- @blacai只是不加"ex"
- (例外例)这里没有与一些人断言的相反的实例化。仍在创建异常对象。如果您只想抛出,那么就不需要用变量定义异常对象。在这种情况下从不使用该变量,因此出现了Visual Studio警告。这和代码中其他任何地方一样,都有一个变量定义,但没有赋值。
- 谢谢,你的回答对我很有帮助。
不要这样做,
1 2 3 4 5 6 7 8
| try
{
...
}
catch(Exception ex)
{
throw ex;
} |
您将丢失堆栈跟踪信息…
要么这样做,
1 2
| try { ... }
catch { throw; } |
或
1 2 3 4 5
| try { ... }
catch (Exception ex )
{
throw new Exception ("My Custom Error Message", ex );
} |
如果您处理的是不同的异常,那么您可能需要重新执行的原因之一是例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| try
{
...
}
catch(SQLException sex )
{
//Do Custom Logging
//Don't throw exception - swallow it here
}
catch(OtherException oex )
{
//Do something else
throw new WrappedException ("Other Exception occured");
}
catch
{
System.Diagnostics.Debug.WriteLine("Eeep! an error, not to worry, will be handled higher up the call stack");
throw; //Chuck everything else back up the stack
} |
- 为什么不把捕获物扔出去呢?
- 增加了一个例子来说明为什么,但是,是的,在那个人为的情况下,你可以忽略它。
- 把catch throw;放在特定异常类型catch列表的底部仍然有价值,因为它证明了作者考虑过这种情况,尽管注释可能同样足够。阅读代码时不要猜测是一件好事。
- Eoin,谢谢你简洁(现在完成)的例子…这正是教科书在例外处理章节开始时应该说的……其余的人可以等,但这就是茶杯的问题所在。干杯。基思。
- 出于某种原因,sqlException的名称让我很困扰。
- catch(exception)引发新的异常(…)是您永远都不应该做的事情,因为您混淆了异常信息,并使异常过滤进一步向调用堆栈中增加了不必要的困难。您应该捕获一种类型的异常并抛出另一种类型的异常的唯一时间是在实现抽象层时,并且需要将特定于提供程序的异常类型(例如,sqlException与xmlException)转换为更通用的异常类型(例如,dataloadingException)。
- 如果一个argumentNullException在类使用的代码中的某个深层被抛出,那么它被抛出的唯一原因是客户端向代码中传递了一个空参数。在这种情况下,您不想重置堆栈跟踪吗?如果堆栈跟踪显示nullArgumentException是从客户机直接调用的方法中抛出的(使其明确哪些参数为空),而不是在您自己的代码中嵌入的某个方法(这可能会导致客户机认为您的代码只是有一个bug),我会认为这更清楚……
- @深色"完美"应该在方法的开头预先检查该参数,并在那里抛出argumentNullException(快速失败)。
- 但是在前面添加另一个检查意味着这个论点会被检查两次,对吗?一次是在代码中,另一次是在方法使用的代码中。我知道这些类型的检查是不贵的,但是如果在另一个方法检查并抛出ArgumentNullException时,您只需要捕获(并重新引发)它,那么早期检查它有什么意义呢?
- 我觉得这个代码的WrappedException忘记了包装。作者是否打算将该构造函数中的原始异常作为innerException?
- 根据MS公约,只有两个字母的首字母缩略词应保持大写(IOException),较长的首字母缩略词应以pascalcase(SqlException为基础)。与Java不同(参见EDCOX1×2)。这可能就是SqlException打扰你的原因,@Michaelmyrs。
c(在c 6之前)不支持cil"过滤异常",vb支持,因此在c 1-5中,重新引发异常的一个原因是在catch()时没有足够的信息来确定是否要实际捕获异常。
例如,在VB中可以
1 2 3 4 5
| Try
..
Catch Ex As MyException When Ex.ErrorCode = 123
..
End Try |
…它不会处理具有不同错误代码值的MyException。在V6之前的C中,如果错误代码不是123,则必须捕获并重新抛出MyException:
1 2 3 4 5 6 7 8 9
| try
{
...
}
catch(MyException ex)
{
if (ex.ErrorCode != 123) throw;
...
} |
由于C 6.0,您可以像使用VB一样进行过滤:
1 2 3 4 5 6 7 8
| try
{
// Do stuff
}
catch (Exception e) when (e.ErrorCode == 123456) // filter
{
// Handle, other exceptions will be left alone and bubble up
} |
- 戴夫,但是(至少在Java中)你不会抛出一个"泛型"MyExtExchange,你会定义一个特定的异常类型并抛出它,允许它通过catch块中的类型来区分…但是,是的,如果您不是例外的架构师(我在这里考虑JDBC的SqExExvices(Java)),这是令人讨厌的泛型,并且暴露GETError()方法…六羟甲基三聚氰胺六甲醚。。。你说得有道理,只是我认为在可能的情况下,有更好的方法。干杯。我非常感谢你的时间。基思。
- 好吧,问题是"为什么在C中捕获并重新引发异常?"这是一个答案。=]…即使有专门的异常,异常过滤器也是有意义的:考虑一下您所处的情况,比如,处理一个sqltimeoutException和一个sqlconnectionResetException,这两者都是sqlException。异常过滤器只允许在sqlException是这两者之一时捕获它,因此,您可以"在ex是sqlTimeoutException或ex是sqlConnectionResetException时捕获sqlException ex",而不是将您的try/catch与这两者的处理方式完全相同。(我不是戴夫btw)
- 过滤后的异常出现在C 6中!
我有代码的主要原因如下:
1 2 3 4 5 6 7 8
| try
{
//Some code
}
catch (Exception e)
{
throw;
} |
所以我可以在catch中有一个断点,它有一个实例化的异常对象。我在开发/调试时经常这样做。当然,编译器会给我一个关于所有未使用的e的警告,理想情况下,它们应该在发布构建之前被删除。
不过,在调试期间它们很好。
- 是的,我会付钱的,但是是的,你不想在公开的代码中看到这一点…我会为发表它感到羞愧的;—)
- 实际上,这不是必需的——在Visual Studio中,您可以将调试器设置为在引发异常时中断,它会在Inspector窗口中为您显示异常详细信息。
- 如果您只想在调试期间使用某些代码,请使用if debug…#endif,不需要删除这些行
- 嗯,我自己也做过几次。时不时会有人逃出去。@JammyCakes Visual Studio的Break-on异常的问题是,有时我想要的异常并不是唯一被抛出的异常(甚至是唯一被抛出的类型)。仍然不知道具有"如果被异常跳过则中断"的断点条件。在那之前,这将是有用的。迈克尔·弗里德盖姆:在try {和} catch () {...}周围的#if DEBUG有点乱,坦率地说,这让我很不舒服……一般来说,预处理器不是我的朋友。
重新引发异常的一个有效原因可能是,您希望向异常添加信息,或者可能需要自行生成一个原始异常:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static string SerializeDTO (DTO dto ) {
try {
XmlSerializer xmlSer = new XmlSerializer (dto .GetType());
StringWriter sWriter = new StringWriter ();
xmlSer .Serialize(sWriter, dto );
return sWriter .ToString();
}
catch(Exception ex ) {
string message =
String.Format("Something went wrong serializing DTO {0}", DTO );
throw new MyLibraryException (message, ex );
}
} |
- Thanx,是的,异常包装(特别是链子)是完全正常的…不理智的是捕获一个异常,这样你就可以丢弃堆栈跟踪,或者更糟的是,吃掉它。
Isn't this exactly equivalent to not
handling exceptions at all?
不完全一样。它重置异常的stacktrace。虽然我同意这可能是一个错误,因此是一个坏代码的例子。
你不想抛出ex-因为这会丢失调用堆栈。请参阅异常处理(msdn)。
是的,Try…Catch没有做任何有用的事情(除了丢失调用堆栈-所以实际上更糟-除非出于某种原因您不想公开此信息)。
- 在使用throw-ex时,不会丢失整个调用堆栈,只会从异常发生在调用堆栈上方的位置丢失调用堆栈的一部分。但是您保留了从抛出异常的方法到客户机调用它的地方的调用堆栈。实际上,在某些用例中,你可能会使用它,否则微软的好人就不会允许它。也就是说,我没用过。另一个需要记住的问题是抛出异常非常昂贵。只是为了一个非常合理的理由。我认为伐木是合理的,等等。
人们没有提到的一点是,尽管.NET语言没有真正做出适当的区分,但在发生异常时是否应该采取行动以及是否能够解决异常的问题实际上是截然不同的问题。在许多情况下,一个人应该根据自己没有希望解决的异常来采取行动,在某些情况下,"解决"一个异常所必需的全部就是将堆栈展开到某个点——不需要采取进一步的行动。
由于人们普遍认为只有"捕捉"自己能"处理"的东西,很多在发生异常时才应该采取行动的代码是没有的。例如,许多代码会获取一个锁,将被保护对象"暂时"置于违反其不变量的状态,然后将其对象置于合法状态,然后释放在其他人看到对象之前锁定。如果在对象处于危险无效状态时发生异常,通常的做法是在对象仍处于该状态时释放锁。更好的模式是,当对象处于"危险"状态时,会出现一个异常,明确地使锁失效,因此将来获取它的任何尝试都将立即失败。一致地使用这种模式将大大提高所谓的"pokemon"异常处理的安全性,imho之所以名声不好,主要是因为代码允许异常在不首先采取适当措施的情况下渗透。
在大多数.NET语言中,代码根据异常采取操作的唯一方法是对catch它(即使它知道它不会解决异常),执行有问题的操作,然后重新执行throw。如果代码不关心抛出什么异常,另一种可能的方法是使用带有try/finally块的ok标志;在该块之前将ok标志设置为false,在该块退出之前和在该块内的任何return之前设置为true。然后,在finally中,假设没有设置ok时,一定发生了异常。这种方法在语义上比catch/throw好,但它很难看,而且维护性比它应该的要差。
这取决于您在catch块中所做的操作,以及您是否希望将错误传递给调用代码。
您可能会说Catch io.FileNotFoundExeption ex,然后使用其他文件路径或类似路径,但仍然会引发错误。
另外,使用Throw而不是throw ex,可以保留完整的堆栈跟踪。throw ex从throw语句重新启动堆栈跟踪(我希望这是有意义的)。
捕获throw的一个可能原因是禁用堆栈中更深层次的任何异常过滤器来过滤(随机的旧链接)。但是,当然,如果这是意图的话,那里会有评论这样说。
- 直到我看了链接我才知道你在哪里…我还是不确定你在说什么…我完全不熟悉vb.net。我认为这会导致总数被报告为"不一致",对吧?…我非常喜欢静态方法。除了简单之外,如果您将属性设置与实际工作的代码分开,则不一致的可能性会更小。堆栈是"自我清理"。
- 人们期望当他们写"try foo();finally bar();"时,foo和bar之间不会有任何运行。但这不是真的;如果调用者添加了异常过滤器,并且没有中间的"catch"和foo()引发,那么调用者的其他一些随机代码将在最终(bar)运行之前运行。如果您已经破坏了不变量或提高了安全性,那么这是非常糟糕的,期望它们最终会"立即"恢复到正常状态,并且没有其他代码会看到临时更改。
这在库或DLL的编程函数中很有用。
此rethrow结构可用于有目的地重置调用堆栈,这样,您就可以从函数本身获得异常,而不是从函数内部的单个函数抛出异常。
我认为这只是为了让抛出的异常更干净,而不进入库的"根"中。
虽然其他许多答案都提供了很好的例子,说明为什么您可能希望捕获一个重新引发的异常,但似乎没有人提到"最终"场景。
例如,您有一个方法,其中设置了光标(例如等待光标),该方法有多个退出点(例如,if()return;),您希望确保在方法末尾重置光标。
要做到这一点,您可以在try/catch/finally中包装所有代码。在最后将光标设置回右光标。这样,您就不会隐藏任何有效的异常,而是在catch中重新执行它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| try
{
Cursor.Current = Cursors.WaitCursor;
// Test something
if (testResult) return;
// Do something else
}
catch
{
throw;
}
finally
{
Cursor.Current = Cursors.Default;
} |
- catch是try...finally历史上的强制性部分,还是在本例中起作用?-我只是反复检查了一下,我可以使用try {} finally {},而不需要使用catch块。
在您发布的代码中的示例中,事实上,捕获异常没有意义,因为在捕获上没有任何操作,它只是重新定义,事实上,当调用堆栈丢失时,它弊大于利。
但是,如果发生异常,您会捕获一个异常来执行一些逻辑(例如关闭文件锁的SQL连接,或者只是一些日志记录),然后将其抛出到要处理的调用代码中。这在业务层中比前端代码更常见,因为您可能希望实现业务层的编码人员处理异常。
要重新迭代,在您发布的示例中捕捉异常是没有意义的。别那样做!
不好意思,许多"改进设计"的例子闻起来还是很难闻,或者极易引起误解。尝试捕获日志;抛出完全没有意义。异常日志记录应该在应用程序的中心位置进行。无论如何,异常都会在stacktrace上冒泡,为什么不将它们记录在系统边界附近的某个位置呢?
当您将上下文(即给定示例中的dto)序列化到日志消息中时,应该小心。它可以很容易地包含敏感信息,人们可能不想接触到所有可以访问日志文件的人。如果您不向异常添加任何新信息,我真的看不到异常包装的意义。好的Java有点意义,它需要调用方知道应该调用什么样的异常,然后调用代码。因为你在.NET中没有这个功能,所以包装在我见过的至少80%的情况下没有任何效果。
- 谢谢你的想法,乔。在Java中(我猜想是C),我希望看到一个类级别的注释@ FaultBoundary,它强制所有的异常(包括未检查的异常类型)被捕获或声明被抛出。我将在每个体系结构层的公共接口上使用这个注释。因此,@faultboundary thingdao接口将无法泄漏实现细节,如sqleexceptions、npe或aiob。相反,"因果"stacktrace将被记录并引发daosystemexception…我将系统异常定义为"永久致命"。
- 有很多原因需要捕捉、记录,然后再传播。特别是,如果catch日志中的方法具有信息,那么一旦退出该方法,就会丢失这些信息。错误可能会在以后处理,但不会被记录,并且您已经丢失了关于系统缺陷的信息。
- 这就是异常类的数据属性非常方便的地方——为通用日志记录捕获所有本地信息。这篇文章最初引起了我的注意:blog.abodit.com/2010/03/…
除了其他人所说的之外,请看我对相关问题的回答,这个问题表明捕获和重新执行不是一个"不操作"(它在vb中,但有些代码可以从vb中调用)。
- 虽然这个链接可以回答问题,但最好在这里包含答案的基本部分并提供链接以供参考。如果链接页发生更改,则仅链接答案可能无效。-从审查
- @哈姆扎尔,我同意这不是一个写得很好的答案,但它有信息,不同于其他答案和积极的投票。所以我不明白,你为什么建议删除它?"主题和给出解决方案的简短答案仍然是答案。"来自meta.stackexchange.com/questions/226258/…
- 这是仅链接的答案
- 1。仅链接答案应更改为评论,而不是删除。2。这是另一个这样的问题,而不是外部网站,它认为随着时间的推移不太可能被破坏。三。它有一些额外的描述,这使得它不是"仅链接"——参见meta.stackexchange.com/questions/225370/…
大多数答案都是关于场景捕获日志的。
不要把它写在代码中,而是考虑使用AOP,特别是Postharp.diagnostic.toolkit和OnExceptionOptions includeParameterValue以及包含文档
- 虽然这个链接可以回答问题,但最好在这里包含答案的基本部分并提供链接以供参考。如果链接页发生更改,则仅链接答案可能无效。-从审查
- @汤元东,我同意这不是一个写得好的答案,但它有信息,不同于其他答案和积极的投票。所以我不明白,你为什么建议删除它?顺便说一句,5年后的链接仍然有效。"主题和给出解决方案的简短答案仍然是答案。"来自meta.stackexchange.com/questions/226258/…
- StackOverflow只有这个建议。
- @Tonydong,如果答案不是绝对无用,你应该选择"看起来不错"