2012-06-25 57 views
16

问题: C#中的普通throw语句是否会导致一个新的异常本身?C#中的(普通)throw语句是否会导致异常?


注意,我问这个问题出于好奇,不是因为我有任何实际或真实世界的情况会很多事。另外请注意,我的直觉和经验告诉我答案是“否”,但我希望以某种方式验证该答案(有关我迄今为止尝试过的资料,请参阅下文)。

下面是一些示例代码来说明我的问题:

try 
{ 
    int x = 0, y = 1/x; 
} 
catch (Exception outerException) 
{ 

    try 
    { 
     throw; 
    } 
    catch (Exception innerException) 
    { 
     // Q: Does this Assert ever fail?? 
     System.Diagnostics.Debug.Assert(outerException.Equals(innerException)); 
    } 
} 

我不知道是否有任何方式都改变使得Assert会失败的情况下,不接触内try/catch块。

我已经试过或正在寻找,试图回答这个问题:

  • 阅读MSDN上的throw (C# Reference)页 - 没有明确的答案;
  • 检查了C# Language Specification的5.3.3.11部分 - 这可能是寻找这种信息的错误地方;
  • 掩盖了我可以尝试触发throw语句的异常。出现OutOfMemoryException异常,但在throw时很难触发。
  • 打开ILDASM检查生成的代码。我可以看到,throw转换为rethrow指令,但我迷失在哪里查看进一步检查该语句是否可以抛出异常。

这就是ILDASM显示内try位:

.try 
{ 
    IL_000d: nop 
    IL_000e: rethrow 
} // end .try 

因此,要总结:可以在throw语句(用于重新抛出异常)不断原因异常本身?

+6

除了令人满意的好奇心,你想解决什么问题? – Oded

+2

只是好奇心我恐怕没有任何令人兴奋的(只要例外甚至可以令人兴奋)与此有关的现实世界的场景。 – Jeroen

+0

我确实认为如果你的堆栈损坏可能会发生。在这种情况下,rethrow将尝试使用catch/finally/fault子句将堆栈展开到下一个方法。如果无法处理,将导致异常,但更有可能是ExecutionEngineException,这将导致正常的流程立即终止。 –

回答

7

C#中的普通throw语句是否会导致一个新的异常本身?

根据定义它不会。 throw;的最后一点是保留活动异常(特别是堆栈跟踪)。

从理论上讲,一个实现可能会克隆这个异常,但是什么意思呢?

+0

感谢您花时间回答! *“按定义它不会”*正是我所期望的,但是当我在我的问题中写下时,我找不到任何(部分)定义,这消除了我对此的疑惑。另外,@AloisKraus对这个问题的评论表明,它仍然有可能得到抛出异常。也许有人有一个可以帮助的参考? – Jeroen

+0

我认为阿洛伊斯正在驳斥Gabe的建议。目前还不清楚你在找什么。 –

+0

对不起,你对评论正确,我的不好! – Jeroen

1

您的断言永远不会失败,因为重新抛出和断言之间没有代码。如果发现异常并导致另一个异常,异常会发生变化 - 唯一的方法就是例如。通过在你的catch子句中有错误的代码或“抛出新的”。

+0

我知道重新抛出和断言之间没有代码。这是故意的,因为我的问题是'throw'本身是否会导致异常。我会尝试更新我的问题以澄清这一点。 – Jeroen

+0

没有有意义的“托管”代码,但CLR仍然需要运行一些代码来处理重新抛出,这本身可能会导致一些异常。 –

15

在我的老实看法,理论上断言可能'失败'(实际上我不这么认为)。

怎么样?

注意:以下仅仅是我对之前在SSCLI上做过的一些研究的基础上的“意见”。

  1. 可能发生InvalidProgramException。无可否认,这是非常不可能的,但理论上可行(例如,某些内部CLR错误可能导致可丢弃对象变得不可用!!!!)。
  2. 如果CLR没有找到足够的内存来处理're-throw'动作,它将会抛出一个OutOfMemoryException异常(CLR的内部重抛逻辑需要分配一些内存,如果它没有处理'预分配' OutOfMemoryException异常)。
  3. 如果CLR在某些其他主机下运行(例如SQL服务器或甚至您自己的主机),并且主机决定终止异常重新抛出线程(基于某些内部逻辑)ThreadAbortException(称为粗线程中止在这种情况下)将会被提升。虽然,我不确定Assert是否会在这种情况下执行。
  4. 自定义主机可能已将升级策略应用于CLR(ICLRPolicyManager::SetActionOnFailure)。在这种情况下,如果您正在处理OutOfMemoryException,升级策略可能会导致发生ThreadAbortException(再次粗暴线程中止。不确定如果策略指示正常线程中止会发生什么情况)。
  5. 尽管@Alois Kraus澄清了'正常'线程中止异常是不可能的,但从SSCLI研究中我仍然怀疑(正常)ThreadAbortException可能发生。

编辑:

正如我刚才所说,该断言理论上可以失败,但实际上它是高度不可能的。因此,为此开发POC非常困难。 为了提供更多'证据',以下是处理rethow IL指令的SSCLI代码片段,它验证了我的以上几点。

警告:商业CLR可能与SSCLI有很大差异。

  1. InvalidProgramException:

    if (throwable != NULL) 
    { 
    ... 
    } 
    else 
    { 
        // This can only be the result of bad IL (or some internal EE failure). 
        RealCOMPlusThrow(kInvalidProgramException, (UINT)IDS_EE_RETHROW_NOT_ALLOWED); 
    } 
    
  2. 粗鲁的线程中止:

    if (pThread->IsRudeAbortInitiated()) 
    { 
        // Nobody should be able to swallow rude thread abort. 
        throwable = CLRException::GetPreallocatedRudeThreadAbortException(); 
    } 
    

    这意味着,如果 '粗鲁的线程中止' 已经启动,任何异常得到改变粗鲁的线程中止异常。

  3. 现在最有趣的是,OutOfMemoryException。由于重新抛出IL指令本质上是重新抛出相同的异常对象(即object.ReferenceEquals返回true),因此在重新抛出时可能会发生OutOfMemoryException。但是,下面的SSCLI代码表明,它是可能的:

    // Always save the current object in the handle so on rethrow we can reuse it. This is important as it 
    // contains stack trace info. 
    // 
    // Note: we use SafeSetLastThrownObject, which will try to set the throwable and if there are any problems, 
    // it will set the throwable to something appropiate (like OOM exception) and return the new 
    // exception. Thus, the user's exception object can be replaced here. 
    
    throwable = pThread->SafeSetLastThrownObject(throwable); 
    

    SafeSetLastThrownObject调用SetLastThrownObject如果失败引发OutOfMemoryException。下面是从SetLastThrownObject的片段(加上我的意见)

    ... 
    if (m_LastThrownObjectHandle != NULL) 
    { 
        // We'll somtimes use a handle for a preallocated exception object. We should never, ever destroy one of 
        // these handles... they'll be destroyed when the Runtime shuts down. 
        if (!CLRException::IsPreallocatedExceptionHandle(m_LastThrownObjectHandle)) 
        { 
        //Destroys the GC handle only but not the throwable object itself 
        DestroyHandle(m_LastThrownObjectHandle); 
        } 
    } 
    ... 
    
    //This step can fail if there is no space left for a new handle 
    m_LastThrownObjectHandle = GetDomain()->CreateHandle(throwable); 
    

    上面的代码片段显示,Throwable对象的GC手柄被破坏(即释放了GC表中的槽),然后创建一个新的句柄。由于一个插槽刚刚被释放,所以新的句柄创建将永远不会失败,直到在非常罕见的新线程场景中正确安排时间并消耗所有可用的GC句柄为止。

除此之外,通过RaiseException win api提出了所有例外(包括反馈)。捕获此异常以准备相应的托管异常的代码本身可以引发OutOfMemoryException

+0

这个回答很好,谢谢!关于你的几点观点的推理似乎是合乎逻辑的,我倾向于遵循它。如果我们可以添加一些示例应用程序和重现步骤来显示它可能发生,那么答案当然会更加明确。我可能会稍后尝试验证您的一个场景,并让您知道我发现了什么。 – Jeroen

+0

有趣的编辑。棘手的问题当然是:这种行为是否正确和/或是否合规;老实说,我不知道答案。但是,好的阅读,欢呼。 –

+0

这正是我为什么要提出这样的警告:)....尽管就商业CLR的行为而言,我猜测通过附加一个分析器可能会检查在处理重新引发IL指令时是否分配新的句柄,或者不。 –

5

我怀疑你错过了位可能是rethrow的规范,这是内ECMA-335,分区III,第4.24节:

4.24重新抛出 - 重新抛出当前异常

说明:
只有在捕获处理程序的主体内才允许重新抛出指令(请参阅 分区I)。它抛出与此处理程序捕获的异常相同。 重新抛出不会更改对象中的堆栈跟踪。

例外:
引发原始异常。

(重点煤矿)

所以,是的,它看起来像你的断言保证依据规范的工作。 (当然,这是假设的实现遵循规范...)

的C#规范的相关部分是部分8.9.5(C#4版):

不带表达式的throw语句可以仅在catch块中使用,在这种情况下,该语句会重新引发当前由该catch块处理的异常。

再次,表明原来的异常和这将引发异常。

(第5.3.3.11你提到只是说说明确分配,throw语句本身的不行为)。

这一切都不无效Amit的点,当然,这是针对一些情况有些在任何地方指定的范围之外。 (当主机应用其他规则时,语言规范很难考虑到这些规则。)

+1

感谢乔恩,那是关于我在找什么的。除非有人想出一个反例,赏金可能会去这里:-) – Jeroen

+0

我要指出的是,这是C#语言的规范。在实际中,这取决于实施。 Mono和.net CLR可能会有非常不同的答案,唯一的解决方法就是在这些环境中实际并故意地测试它。 – whiskeyfur

+0

经过Amit的编辑之后,感觉很难选择奖赏奖励的地方。我给了他最初的赏金,并且想为这个做另外一个平等的100代表赏金(“答案值得额外信贷”的原因),但由于某种原因,该系统只允许我从200点起做赏金,所以一旦我离开了移动互联网,我将不得不考虑这一点。无论如何:非常感谢您的好评! – Jeroen

0

结合递归平原throw可以很容易地在64位平台上导致StackOverflowException

class Program 
{ 
    // expect it to be 10 times less in real code 
    static int max = 455; 

    static void Test(int i) 
    { 
     try { 
      if (i >= max) throw new Exception("done"); 
      Test(i + 1); 
     } 
     catch { 
      Console.WriteLine(i); 
      throw; 
     } 
    } 

    static void Main(string[] args) 
    { 
     try { 
      Test(0); 
     } 
     catch { 
     } 
     Console.WriteLine("Done."); 
    } 
} 

在控制台:

... 
2 
1 
0 

Process is terminated due to StackOverflowException. 

一些解释可能会发现here

相关问题