我用“throw;”重新抛出异常,但堆栈跟踪不正确:
static void Main(string[] args) {
try {
try {
throw new Exception("Test"); //Line 12
}
catch (Exception ex) {
throw; //Line 15
}
}
catch (Exception ex) {
System.Diagnostics.Debug.Write(ex.ToString());
}
Console.ReadKey();
}
正确的堆栈跟踪应该是:
System.Exception: Test at ConsoleApplication1.Program.Main(String[] args) in Program.cs:Line 12
但我明白了:
System.Exception: Test at ConsoleApplication1.Program.Main(String[] args) in Program.cs:Line 15
但第15行是“throw;”的位置。我用.NET 3.5进行了测试。
答案 0 :(得分:27)
在同一方法中投掷两次可能是一种特殊情况 - 我无法创建堆栈跟踪,其中同一方法中的不同行彼此跟随。正如单词所说,“堆栈跟踪”向您显示异常遍历的堆栈帧。每个方法调用只有一个堆栈帧!
如果您使用其他方法,throw;
将不会按预期删除Foo()
的条目:
static void Main(string[] args)
{
try
{
Rethrower();
}
catch (Exception ex)
{
Console.Write(ex.ToString());
}
Console.ReadKey();
}
static void Rethrower()
{
try
{
Foo();
}
catch (Exception ex)
{
throw;
}
}
static void Foo()
{
throw new Exception("Test");
}
如果修改Rethrower()
并将throw;
替换为throw ex;
,则堆栈跟踪中的Foo()
条目将消失。再次,这是预期的行为。
答案 1 :(得分:24)
这可以被认为是预期的。
如果指定throw ex;
,则通常会修改堆栈跟踪,FxCop将通知您堆栈已被修改。如果您生成throw;
,则不会生成警告,但仍会修改跟踪。
所以不幸的是,现在最好不要抓住前任或将其作为内部投掷。
我认为它应该被视为 Windows影响或者像 - 编辑那样。
Jeff Richter 在他的“CLR中通过C#”更详细地描述了这种情况:
以下代码抛出相同的内容 它捕获的异常对象和 导致CLR重置其启动 异常点:
相反,如果你重新抛出一个 使用throw的异常对象 关键字本身,CLR没有 重置堆栈的起点。该 下面的代码重新抛出相同的内容 它捕获的异常对象, 导致CLR无法重置它 例外的起点:private void SomeMethod() { try { ... } catch (Exception e) { ... throw e; // CLR thinks this is where exception originated. // FxCop reports this as an error } }
事实上,唯一的区别 这两个代码片段是什么的 CLR认为是原始位置 抛出异常的地方。 不幸的是,当你扔或者 Windows会重新抛出异常 重置堆栈的起点。所以 如果异常变得无法处理, 报告的堆栈位置 到Windows错误报告是 最后一次投掷的位置或 重新抛出,即使CLR知道 原始堆栈的位置 异常被抛出。这是 不幸的是因为它会调试 失败的应用程序 场难得多。一些 开发人员已经发现了这一点 无法忍受他们选择了一个 实现其代码的不同方式 确保堆栈跟踪真正 反映了一个地方 异常最初抛出:private void SomeMethod() { try { ... } catch (Exception e) { ... throw; // This has no effect on where the CLR thinks the exception // originated. FxCop does NOT report this as an error } }
private void SomeMethod() { Boolean trySucceeds = false; try { ... trySucceeds = true; } finally { if (!trySucceeds) { /* catch code goes in here */ } } }
答案 2 :(得分:19)
这是Windows版本的CLR中众所周知的限制。它使用Windows内置的异常处理支持(SEH)。问题是,它是基于堆栈帧的,并且方法只有一个堆栈帧。您可以通过将内部try / catch块移动到另一个辅助方法中来轻松解决问题,从而创建另一个堆栈帧。此限制的另一个后果是JIT编译器不会内联任何包含try语句的方法。
答案 3 :(得分:10)
如何保留REAL堆栈跟踪?
您抛出一个新异常,并将原始异常包含为内部异常。
但是这很丑......更长......让你选择抛出的例外......
你错了丑陋,但对其他两点是对的。经验法则是:除非你打算用它做什么,否则不要抓住它,比如包装它,修改它,吞下它或记录它。如果您再次决定catch
然后throw
,请确保您正在使用它,否则只是让它冒泡。
您可能也很想放置一个catch,因此您可以在catch中使用断点,但Visual Studio调试器有足够的选项可以使该练习不必要,请尝试使用第一次机会异常或条件断点。
答案 4 :(得分:7)
修改/替换强>
这种行为实际上是不同的,但却是极端的。至于为什么行为如果不同,我需要推荐给CLR专家。
编辑:AlexD's answer似乎表明这是设计上的。
在捕获它的同一方法中抛出异常会使情况稍微混淆,所以让我们从另一个方法抛出异常:
class Program
{
static void Main(string[] args)
{
try
{
Throw();
}
catch (Exception ex)
{
throw ex;
}
}
public static void Throw()
{
int a = 0;
int b = 10 / a;
}
}
如果使用throw;
,则callstack(行号被代码替换):
at Throw():line (int b = 10 / a;)
at Main():line (throw;) // This has been modified
如果使用throw ex;
,则callstack为:
at Main():line (throw ex;)
如果未捕获异常,则callstack为:
at Throw():line (int b = 10 / a;)
at Main():line (Throw())
在.NET 4 / VS 2010中测试
答案 5 :(得分:5)
有一个重复的问题here。
据我了解 - 扔;被编译成'rethrow' MSIL instruction并且它修改了堆栈跟踪的最后一帧。
我希望它保留原始堆栈跟踪并添加重新抛出的行,但显然每个方法调用只能有一个堆栈帧。
结论:避免使用throw;并且在重新投掷时将你的异常换成新的 - 这不是丑陋的,这是最好的做法。
答案 6 :(得分:4)
您可以使用
保留堆栈跟踪ExceptionDispatchInfo.Capture(ex);
以下是代码示例:
static void CallAndThrow()
{
throw new ApplicationException("Test app ex", new Exception("Test inner ex"));
}
static void Main(string[] args)
{
try
{
try
{
try
{
CallAndThrow();
}
catch (Exception ex)
{
var dispatchException = ExceptionDispatchInfo.Capture(ex);
// rollback tran, etc
dispatchException.Throw();
}
}
catch (Exception ex)
{
var dispatchException = ExceptionDispatchInfo.Capture(ex);
// other rollbacks
dispatchException.Throw();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.InnerException.Message);
Console.WriteLine(ex.StackTrace);
}
Console.ReadLine();
}
输出类似于:
Test app ex Test inner ex at TestApp.Program.CallAndThrow() in D:\Projects\TestApp\TestApp\Program.cs:line 19 at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 30 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 38 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 47
答案 7 :(得分:3)
好吧,.NET Framework中似乎存在一个错误,如果你抛出一个异常,并在同一个方法中重新抛出它,原始行号就会丢失(它将是该方法的最后一行)。 / p> 幸运的是,一个名叫Fabrice MARGUERIE的聪明人找到了这个bug的a solution。以下是我的版本,您可以在this .NET Fiddle中进行测试。
private static void RethrowExceptionButPreserveStackTrace(Exception exception)
{
System.Reflection.MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
preserveStackTrace.Invoke(exception, null);
throw exception;
}
现在通常会捕获异常,而不是抛出;只需调用此方法,瞧,原始行号将被保留!
答案 8 :(得分:2)
不确定这是否符合设计,但我认为一直都是这样。
如果原始throw new Exception在一个单独的方法中,那么throw的结果应该是原始方法名和行号,然后是main中重新抛出异常的行号。
如果你使用throw ex,那么结果将只是main中重新抛出异常的行。
换句话说,抛弃ex strong all 堆栈跟踪,而throw保留堆栈跟踪历史(即低级方法的详细信息)。但是,如果您的异常是通过与重新抛出相同的方法生成的,那么您可能会丢失一些信息。
NB。如果你编写一个非常简单和小的测试程序,框架有时可以优化事物并将方法更改为内联代码,这意味着结果可能与“真实”程序不同。
答案 9 :(得分:1)
您想要正确的行号吗?只需使用 一个 try / catch per方法。在系统中,嗯...只是在UI层,而不是在逻辑或数据访问中,这非常烦人,因为如果你需要数据库事务,那么它们不应该在UI层中,你也不会正确的行号,但如果你不需要它们,不要在捕获中重新抛出,也不要没有例外......
5分钟示例代码:
菜单文件 - > 新建项目,放置三个按钮,并在每个按钮中调用以下代码:
private void button1_Click(object sender, EventArgs e)
{
try
{
Class1.testWithoutTC();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + Environment.NewLine + "In. Ex.: " + ex.InnerException);
}
}
private void button2_Click(object sender, EventArgs e)
{
try
{
Class1.testWithTC1();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + Environment.NewLine + "In. Ex.: " + ex.InnerException);
}
}
private void button3_Click(object sender, EventArgs e)
{
try
{
Class1.testWithTC2();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + Environment.NewLine + "In. Ex.: " + ex.InnerException);
}
}
现在,创建一个新类:
class Class1
{
public int a;
public static void testWithoutTC()
{
Class1 obj = null;
obj.a = 1;
}
public static void testWithTC1()
{
try
{
Class1 obj = null;
obj.a = 1;
}
catch
{
throw;
}
}
public static void testWithTC2()
{
try
{
Class1 obj = null;
obj.a = 1;
}
catch (Exception ex)
{
throw ex;
}
}
}
运行......第一个按钮很漂亮!
答案 10 :(得分:0)
我认为这不是堆栈跟踪更改的情况,而是更多地与确定堆栈跟踪的行号的方式有关。在Visual Studio 2010中试用它,行为类似于您对MSDN文档的期望:“throw ex;”从这个语句的角度重建堆栈跟踪,“throw;”除了重新抛出异常的地方之外,行号是重新抛出的位置,而不是异常通过的调用。
所以用“扔;”方法调用树保持不变,但行号可能会改变。
我偶然遇到过这种情况,它可能是设计上的,并没有完整记录。我可以理解为什么他们可能已经这样做,因为重新抛出位置非常有用,如果你的方法很简单,原始来源通常都是显而易见的。
正如许多其他人所说,除非你真的需要,否则通常最好不要捕捉异常,和/或你将在那时处理它。
有趣的注意事项:Visual Studio 2010甚至不会让我构建问题中提供的代码,因为它在编译时选择了零除错误。
答案 11 :(得分:-2)
这是因为您从第12行抓取了Exception
并在第15行上重新播放了它,因此Stack Trace将其视为现金,即Exception
从那里被抛出。
为了更好地处理异常,您应该只使用try...finally
,并让未处理的Exception
冒泡。