为什么c#有单独的重新抛出语句(throw;)而不是重载throw ex ?

本文关键字:throw ex 重载 单独 语句 新抛出 为什么 | 更新日期: 2023-09-27 18:08:55

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

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

那么,考虑到上面的问题,在c#中使用单独的throw;语句的优势是什么呢?或者,换句话说:为什么c#使用一个特殊的单独的语句(throw;),这是不太为人所知的,对于最常用的情况(当用户想要保留stacktrace),它使用更自然的throw ex;对于不太常见的情况(当stacktrace被清除时)?还有其他我没有提到的用例吗?

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

为什么c#有单独的重新抛出语句(throw;)而不是重载throw ex ?

中抛出变量的原因
catch (SomeExceptionType ex)
{
    frobnicate(ex);
    throw;
    throw ex;
}

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

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

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

(您可能仍然希望执行过滤深度复制,以防在其他异常属性中泄露信息,例如InnerException。但是c#允许你跳过复制步骤,如果你选择的话。)

1本地库的实现细节可以通过其他方式发现。保护这些细节需要一个类似web服务的执行模型,在这个模型中,对代码的完全控制被维护。

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

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

  1. rethrow抛出的异常是当前正在处理的异常实例。
  2. rethrow指令保留原来的堆栈跟踪。

这些语义上的差异要求c#要么提供两种不同的抛出异常的形式,要么取消c#用户能够在预期的重抛出操作期间保留原始异常堆栈跟踪的能力(不要与rethrow字节码指令混淆,throw;语法专门使其可访问)。