为什么在C#中捕获并重新抛出exception?

我正在看可序列化DTO上的文章C# – 数据传输对象

文章包含这段代码:

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),但是try-catch-throw会抛出WtfException … 这不完全等同于不处理exception吗?

人机工程学:

 public static string SerializeDTO(DTO dto) { XmlSerializer xmlSer = new XmlSerializer(dto.GetType()); StringWriter sWriter = new StringWriter(); xmlSer.Serialize(sWriter, dto); return sWriter.ToString(); } 

或者我错过了C#中的error handling的基本知识? 它几乎与Java相同(减去检查的exception),不是吗? …也就是说,他们都提炼了C ++。

堆栈溢出问题重新抛出无参数捕获和不做任何事情之间的区别? 似乎支持我的观点,即尝试抛出是无效的。


编辑:

只是为了总结任何未来发现这个线程的人…

不要

 try { // Do stuff that might throw an exception } catch (Exception e) { throw e; // This destroys the strack trace information! } 

堆栈跟踪信息对于确定问题的根本原因至关重要!

 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一样)。


参考文献:

  • MSDN – exception处理
  • MSDN – try-catch(C#参考)

第一; 在文章中的代码的方式是邪恶的。 throw ex会将exception中的调用堆栈重置为这个throw语句所在的位置; 失去了关于实际创buildexception的信息。

其次,如果你只是像这样捕获并重新抛出,我没有看到任何附加价值,上面的代码示例将是一样的好(或者,如果没有尝试捕获,甚至更好)。

但是,有些情况下您可能想要捕捉并重新抛出exception。 logging可能是其中之一:

 try { // code that may throw exceptions } catch(Exception ex) { // add error logging here throw; } 

不要这样做,

 try { ... } catch(Exception ex) { throw ex; } 

你会失去堆栈跟踪信息…

要么做,

 try { ... } catch { throw; } 

要么

 try { ... } catch (Exception ex) { throw new Exception("My Custom Error Message", ex); } 

你可能想要重新抛出的原因之一是,如果你正在处理不同的例外,例如

 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 } 

C#(C#6之前)不支持CIL“过滤的exception”,这是VB所做的,因此在C#1-5中,重新抛出exception的一个原因是您没有足够的信息在catch()以确定您是否真的想要发现exception。

例如,在VB中你可以做

 Try .. Catch Ex As MyException When Ex.ErrorCode = 123 .. End Try 

…不能用不同的ErrorCode值处理MyExceptions。 在v6之前的C#中,如果ErrorCode不是123,则必须捕获并重新抛出MyException:

 try { ... } catch(MyException ex) { if (ex.ErrorCode != 123) throw; ... } 

由于C#6.0可以像使用VB一样进行过滤 :

 try { // Do stuff } catch (Exception e) when (e.ErrorCode == 123456) // filter { // Handle, other exceptions will be left alone and bubble up } 

重新抛出exception的一个合理原因可能是您想要将信息添加到exception中,或者可能将原始exception包装在您自己制作的exception中:

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

我有像这样的代码的主要原因:

 try { //Some code } catch (Exception e) { throw; } 

是这样的,我可以在catch中有一个断点,它有一个实例化的exception对象。 我在开发/debugging时做了很多工作。 当然,编译器会给所有未使用的e提供一个警告,理想情况下应该在发布之前删除它们。

他们在debugging过程中很好。

这不完全等同于不处理exception吗?

不完全一样,这是不一样的。 它重置exception的堆栈跟踪。 虽然我同意这可能是一个错误,因此也是一个不好的代码的例子。

你不想扔掉前 – 因为这将失去调用堆栈。 请参阅exception处理 (MSDN)。

是的,try … catch没有任何用处(除了失去调用堆栈 – 实际上更糟 – 除非由于某种原因你不想公开这些信息)。

人们没有提到的一点是,尽pipe.NET语言并没有真正做出区分,但是在发生exception时是否应该采取行动 ,以及是否能够解决这个问题的问题实际上是不同的问题。 在很多情况下,我们应该根据例外情况采取行动,而这些例外是没有希望解决的,而且在某些情况下,“解决”例外所需的一切就是将堆栈展开到某一点 – 不需要采取进一步行动。

由于人们只能“捕捉”可以“处理”的东西的普遍看法,很多在exception发生时应该采取行动的代码并不是。 例如,很多代码会获得一个锁,把被保护对象“暂时”置于一个违反它的不variables的状态,然后把它的对象置于合法的状态,然后在别人看不到对象之前释放锁。 如果在对象处于危险无效状态时发生exception,通常的做法是释放对象仍处于该状态的锁。 一个好得多的模式是在对象处于“危险”状态时发生exception,明确地使锁无效,以便将来尝试获取它将立即失败。 一致地使用这种模式将大大提高所谓的“口袋妖怪”exception处理的安全性,恕我直言,主要是因为代码,允许例外渗透,而不采取适当的行动第一,声誉不佳。

在大多数.NET语言中,代码根据exception采取行动的唯一方法是catch它(即使知道它不会解决exception),执行相关操作,然后重新throw 。 如果代码不关心抛出什么exception,另一种可能的方法是在try/finally块中使用ok标志; 在块之前将ok标志设置为false ,在块退出之前以及在块内的任何return之前将其设置为true 。 然后,在finally ,假设如果ok没有设置,则必须发生exception。 这样的方法在语义上比catch / throw要好,但是丑陋,并且不如应有的可维护性。

这取决于你在catch块中做什么,如果你想把错误传递给调用代码。

你可能会说Catch io.FileNotFoundExeption ex ,然后使用替代文件path或一些这样的,但仍抛出错误。

此外,使用Throw而不是Throw Ex允许您保留完整的堆栈跟踪。 抛出ex重新从throw语句的堆栈跟踪(我希望是有道理的)。

在你发布的代码的例子中,事实上,捕获exception是没有意义的,因为catch没有做任何事情,只是重新抛出,事实上,它会造成更多的伤害,而不是调用堆栈丢失。

但是,如果发生exception,可能会捕获一个exception来执行一些逻辑(例如,closures文件锁的sql连接,或者只是一些日志logging),然后将其返回到调用代码进行处理。 这在业务层中比在前端代码中更常见,因为您可能希望编码器实现业务层来处理exception。

重新迭代,虽然在你发布的例子中捕获exception没有意义。 不要这样做!

捕捉抛出的一个可能的原因是禁止任何从堆栈更深的filter( 随机旧链接 )中的exceptionfilter。 但是,当然,如果这是这个意图的话,那么会有这样的评论。

对不起,但很多“改进devise”的例子仍然闻到可怕或可能是非常误导。 尝试{} catch {log; 扔}是完全没有意义的。 exceptionlogging应该在应用程序的中心位置完成。 无论如何,exception会触发堆栈跟踪,为什么不把它们logging到某个地方并靠近系统的边界呢?

当您将您的上下文序列化(即在一个给定的例子中的DTO)时,应该谨慎使用日志消息。 它可以很容易地包含敏感信息,而这些信息可能不想传到所有可以访问日志文件的人手中。 如果你不添加任何新的信息到exception,我真的没有看到exception包装的重点。 好的老Java有一点意义,它要求调用者知道什么样的例外应该期望然后调用代码。 由于.NET中没有这个function,所以在我看到的至less80%的情况下,包装并没有起到任何作用。

除了别人所说的之外,看到我对一个相关问题的回答 ,它显示捕获和重新抛出不是无操作(它是在VB中,但是一些代码可能是从VB调用的C#)。

大部分答案都是关于场景catch-log-rethrow。

而不是写在你的代码考虑使用AOP,特别是Postsharp.Diagnostic.Toolkit与OnExceptionOptions IncludeParameterValue和IncludeThisArgument

虽然许多其他的答案提供了很好的例子说明为什么你可能想重新抛出一个exception,似乎没有人提到了“最后”的情况。

这方面的一个例子是你有一个你设置游标的方法(例如等待游标)的方法,该方法有几个退出点(例如if()return;),并且你希望确保游标在方法结束。

要做到这一点,你可以把所有的代码封装在try / catch / finally中。 在终于把光标移回到右边的光标。 所以,你不要埋没任何有效的例外,重新抛出它的捕获。

 try { Cursor.Current = Cursors.WaitCursor; // Test something if (testResult) return; // Do something else } catch { throw; } finally { Cursor.Current = Cursors.Default; }