为什么C#有一个单独的rethrow语句(throw;)而不是重载throw ex?

时间:2014-06-16 18:03:30

标签: c# syntax exception-handling

抛出保留堆栈跟踪的异常是最常见的期望行为,而在Java中,这可以通过throw ex;获得,但必须使用C#throw;。 (另请注意,许多C#程序员经常错误地使用throw ex;代替throw;)。

如果有时必须清除堆栈跟踪(这种情况不太常见),则可能只抛出一个新的异常,如throw new MyException(ex.Message, otherDetails);

那么,考虑到上述情况,在C#中使用单独的throw;语句的优势是什么?

或者,换句话说:为什么C#使用一个特殊的单独语句(throw;),对于最常用的情况(当用户想要保留堆栈跟踪时),它不太为人所知,并使用对于频率较低的情况(清除堆栈跟踪时)更自然throw ex;?是否有其他可能的用例我没有涉及?

C#和Java中的代码示例:

// This is the C# design
try
{
//...
}
catch (MyException ex)
{
   // rethrow so that stack trace is preserved
   ex.AppendData(contextVariable);
   throw;
}
catch (PrivateException ex)
{
   // throw new exception so stack trace is not preserved
   throw new PublicException(ex.Message);
}


// This is the Java design
try
{
//...
}
catch (MyException ex)
{
   // rethrow so that stack trace is preserved
   ex.AppendData(contextVariable);
   throw ex;
   // and I can even choose to use something like, where ProcessException(ex) will return the same ex
   throw ProcessException(ex, contextVariable);
}
catch (PrivateException ex)
{
   // throw new so stack trace is not preserved
   throw new PublicException(ex.getMessage());
}

2 个答案:

答案 0 :(得分:2)

两个抛出变种的原因

catch (SomeExceptionType ex)
{
    frobnicate(ex);
    throw;
    throw ex;
}

存在是因为每个都有用例。只有第一个对于调试发生异常的代码很有用,但在成熟的库中,几乎可以肯定原因是库的外部(硬件/操作系统环境中的故障​​,或者消费者传递的错误参数)图书馆)。在这种情况下,库内部的细节对使用该库的程序员没有帮助,实际上可能会引起更多的混淆。

此外,封闭源库,尤其是在SaaS模型 1 (webservice)中远程访问的库,可能不希望公开实现细节,因为这些库可能包含专有知识产权。在这种情况下,throw ex;更可取。

你说Java throw ex;保留了现有的堆栈跟踪。如何用这种语言避免信息泄漏?除了堆栈跟踪到新的异常对象之外,必须对所有内容进行深层复制,这在抛出更多派生类型的异常时尤其困难。 C#选择使这两种行为都很容易获得。

(您可能仍希望执行过滤后的深层复制,以防信息泄漏到其他异常属性中,例如InnerException。但是,如果您愿意,C#可以跳过复制步骤。)

1 可以通过其他方式发现本地库的实现细节。保护那些能够完全控制代码的细节requires a webservice-like execution model

答案 1 :(得分:1)

throw expression;语句的语义在C#和Java中是不同的。在Java中,异常堆栈跟踪是在实例化异常对象时捕获的。在C#中,在抛出异常对象时捕获异常堆栈跟踪。

此外,CLI(ECMA-335)提供了一个特殊的字节码指令rethrow,它与throw有两个不同的主要方式:

  1. rethrow抛出的异常与当前正在处理的异常实例相同。
  2. rethrow指令保留原始堆栈跟踪。
  3. 这些语义差异要求C#提供两种不同的形式来抛出异常,或者删除C#用户在预期的重新抛出操作期间能够保留原始异常堆栈跟踪的能力(不要与{{1混淆)字节码指令,可由rethrow语法专门访问。