C# 什么可以导致throw重置调用堆栈(我使用的是“throw”,而不是“throw ex”)

C# 什么可以导致throw重置调用堆栈(我使用的是“throw”,而不是“throw ex”),c#,.net,exception,exception-handling,stack-trace,C#,.net,Exception,Exception Handling,Stack Trace,我一直在想“扔”和“扔”的区别 不幸的是,这不是我正在经历的行为;下面是一个简单的示例,再现了我的问题: using System; using System.Text; namespace testthrow2 { class Program { static void Main(string[] args) { try { try

我一直在想“扔”和“扔”的区别

不幸的是,这不是我正在经历的行为;下面是一个简单的示例,再现了我的问题:

using System;
using System.Text;

namespace testthrow2
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                try
                {
                    throw new Exception("line 14");
                }
                catch (Exception)
                {
                    throw; // line 18
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());

            }
            Console.ReadLine();
        }
    }
}
我希望这段代码打印从第14行开始的调用堆栈;但是,调用堆栈从第18行开始。当然,在示例中这没什么大不了的,但是在我的实际应用程序中,丢失初始错误信息是相当痛苦的

我错过了什么明显的东西吗?有没有其他方法可以实现我想要的(即在不丢失堆栈信息的情况下重新抛出异常?)


我正在使用.net 3.5,问题是Windows正在重置堆栈的起点。CLR的行为与预期一致—这只是主机操作系统的异常处理支持的一个限制。问题是每个方法调用只能有一个堆栈帧

您可以将异常处理例程提取到一个单独的“helper”方法中,该方法可以绕过Windows的SEH所施加的限制,但我认为这不一定是一个好主意

在不丢失堆栈信息的情况下重新抛出异常的正确方法是抛出一个新异常,并将捕获到的原始异常作为内部异常


很难想象在很多情况下你真的需要这样做。如果您没有处理异常,只是捕获它以重新显示它,那么您可能不应该首先捕获它。

您应该阅读本文:

简而言之,
throw
通常保留原始抛出异常的堆栈跟踪,但仅当异常未在当前堆栈帧中发生时(即方法)

有一种方法
PreserveStackTrace
(如那篇博客文章所示)可以像下面这样保留原始堆栈跟踪:

try
{

}
catch (Exception ex)
{
    PreserveStackTrace(ex);
    throw;
}
但我通常的解决方案是要么不捕获并重新抛出这样的异常(除非绝对必要),要么总是使用
InnerException
属性抛出新异常来传播原始异常:

try
{

}
catch (Exception ex)
{
     throw new Exception("Error doing foo", ex);
}

正常的重试保留堆栈跟踪上的所有内容,但如果当前方法位于堆栈跟踪中,行号将被覆盖。这是令人讨厌的行为。在C#中,如果一个人需要在异常情况下做一些事情,但不关心异常是什么,那么可以使用以下模式:

Boolean ok = False; try { do_something(); ok = True; } finally { if (!ok) // An exception occurred! handle_exception(); } Dim PendingException As Exception = Nothing; Try Do_Something PendingException = Nothing ' See note Catch Ex As Exception When CopyFirstParameterToSecondAndReturnFalse(Ex, PendingException ) Throw ' Will never execute, since above will return false Finally If PendingException IsNot Nothing Then .. Handle exception EndIf End Try 布尔ok=False; 尝试 { 做某事; ok=正确; } 最后 { if(!ok)//发生异常! handle_exception(); } 这种模式在很多方面非常有用;最常见的是一个函数,它应该返回一个新的IDisposable。如果函数不返回,则必须清理一次性对象。请注意,上述“try”块中的任何“return”语句都必须将ok设置为true

在vb.net中,可以使用一种功能更好的模式,尽管代码中有一点有点讨厌,但该模式:

Boolean ok = False; try { do_something(); ok = True; } finally { if (!ok) // An exception occurred! handle_exception(); } Dim PendingException As Exception = Nothing; Try Do_Something PendingException = Nothing ' See note Catch Ex As Exception When CopyFirstParameterToSecondAndReturnFalse(Ex, PendingException ) Throw ' Will never execute, since above will return false Finally If PendingException IsNot Nothing Then .. Handle exception EndIf End Try Dim PendingException作为异常=无; 尝试 做点什么 PendingException=Nothing'参见注释 CopyFirstParameterToSecondReturnFalse时捕获Ex作为异常(Ex,PendingException) “Throw”将永远不会执行,因为上面将返回false 最后 如果PendingException不是什么,那么 .. 处理异常 恩迪夫 结束尝试 长名称函数应该以明显的方式实现。此模式的优点是使代码可以使用异常。虽然在handle-but-not-catch情况下通常不需要这样做,但有一种情况是非常宝贵的:如果清理例程抛出异常。通常,如果清理例程抛出异常,任何挂起的异常都将丢失。但是,使用上述模式,可以将挂起的异常包装在清理异常中


上述代码有一个有趣的提示:异常可能到达“Catch When”,而Try语句可能正常完成。现在还不清楚在这种情况下会发生什么,但有一点很清楚,Finally语句不应该表现得好像异常正在等待处理一样。清除PendingException将使其生效,这样,如果异常消失,代码将表现为从未发生过。另一种方法是包装并重新显示已知已发生的异常,因为这种情况几乎肯定表明内部异常处理代码有问题。

ok;那么“扔”和“扔”的区别是什么呢;这似乎与你的观点相矛盾answer@Brann:嗯,他们还是不一样
throw ex
重置堆栈跟踪,
throw
不重置堆栈跟踪。您在这里设置的问题是边缘案例。在同一个方法中有异常处理程序,Windows只支持每个方法调用一个堆栈帧。
throw
throw ex
之间的区别在于CLR级别;正如我在回答中所解释的,这在这里是正确的。你只是碰到了主机操作系统的一个限制。好吧,我明白了。因此,如果不是采用相同的方法,这将起作用;是吗?@Brann:是的,如果你把代码拆分成一个单独的助手方法,它会“起作用”。不过,我不会仅仅为了修复您的异常处理例程而这样做。谢谢,您提供的链接非常有用,直接调用InternalPreserveStackTrace可以解决我的问题!如果上面的链接失效,或者有人不想跟随它,那么您只需执行以下操作即可在异常上保留堆栈:
typeof(exception).GetMethod(“InternalPreserveStackTrace”,BindingFlags.Instance | BindingFlags.NonPublic).Invoke(ex,null)我的第一个想法是s