我有这段代码
try
{
//AN EXCEPTION IS GENERATED HERE!!!
}
catch
{
SqlService.RollbackTransaction();
throw;
}
上面的代码在此代码中调用
try
{
//HERE IS CALLED THE METHOD THAT CONTAINS THE CODE ABOVE
}
catch (Exception ex)
{
HandleException(ex);
}
作为参数传递给方法“HandleException”的异常包含堆栈跟踪中“throw”行的行号,而不是生成异常的实际行。任何人都知道为什么会发生这种情况?
EDIT1 好的,谢谢大家的答案。我更改了
的内部捕获
catch(Exception ex)
{
SqlService.RollbackTransaction();
throw new Exception("Enrollment error", ex);
}
现在我在堆栈跟踪上有正确的行,但我不得不创建一个新的异常。我希望找到一个更好的解决方案: - (
EDIT2 也许(如果你有5分钟)你可以尝试这种情况,以检查你是否得到相同的结果,而不是很复杂的重建。
答案 0 :(得分:36)
是的,这是异常处理逻辑中的限制。如果一个方法包含多个抛出异常的throw语句,那么你将得到最后一个抛出的行号。此示例代码重现此行为:
using System;
class Program {
static void Main(string[] args) {
try {
Test();
}
catch (Exception ex) {
Console.WriteLine(ex.ToString());
}
Console.ReadLine();
}
static void Test() {
try {
throw new Exception(); // Line 15
}
catch {
throw; // Line 18
}
}
}
输出:
System.Exception: Exception of type 'System.Exception' was thrown.
at Program.Test() in ConsoleApplication1\Program.cs:line 18
at Program.Main(String[] args) in ConsoleApplication1\Program.cs:line 6
解决方法很简单,只需使用辅助方法来运行可能引发异常的代码。
像这样:
static void Test() {
try {
Test2(); // Line 15
}
catch {
throw; // Line 18
}
}
static void Test2() {
throw new Exception(); // Line 22
}
这种尴尬行为的根本原因是.NET异常处理建立在操作系统对异常的支持之上。在Windows中称为SEH,结构化异常处理。这是基于堆栈帧的,每个堆栈帧只能有一个活动异常。无论方法内的作用域块数是多少,.NET方法都有一个堆栈帧。通过使用帮助器方法,您可以自动获取另一个可以跟踪其自身异常的堆栈帧。当方法包含 throw 语句时,抖动也会自动抑制内联优化,因此不需要显式使用[MethodImpl]属性。
答案 1 :(得分:10)
“但 throw; 保留堆栈跟踪!!使用 throw; ”
你有多少次听说过...那些已经编程了.NET一段时间的人几乎肯定已经听过这个并且可能接受它作为所有并且结束所有'重新抛出'例外。
不幸的是,并非总是如此。正如@hans所解释的,如果导致异常的代码与throw;
语句在同一方法中发生,那么堆栈跟踪将重置为该行。
一种解决方案是将try, catch
中的代码提取到一个单独的方法中,另一种解决方案是将捕获的异常作为内部异常抛出一个新的异常。一个新方法有点笨拙,如果你试图在调用堆栈中进一步捕获它,new Exception()
会丢失原始异常类型。
我发现在Fabrice Marguerie's blog上找到了对此问题的更好描述。
但更好的是另一个StackOverflow问题有解决方案(即使其中一些涉及反射):
In C#, how can I rethrow InnerException without losing stack trace?
答案 2 :(得分:6)
从.NET Framework 4.5开始,您可以使用ExceptionDispatchInfo
类来执行此操作,而无需其他方法。例如,借用Hans'很好的答案,当你只使用throw
时,就像这样:
using System;
class Program {
static void Main(string[] args) {
try {
Test();
}
catch (Exception ex) {
Console.WriteLine(ex.ToString());
}
Console.ReadLine();
}
static void Test() {
try {
throw new ArgumentException(); // Line 15
}
catch {
throw; // Line 18
}
}
}
输出:
System.ArgumentException: Value does not fall within the expected range.
at Program.Test() in Program.cs:line 18
at Program.Main(String[] args) in Program.cs:line 6
但是,您可以使用ExceptionDispatchInfo
来捕获并重新抛出异常,如下所示:
using System;
class Program {
static void Main(string[] args) {
try {
Test();
}
catch (Exception ex) {
Console.WriteLine(ex.ToString());
}
Console.ReadLine();
}
static void Test() {
try {
throw new ArgumentException(); // Line 15
}
catch(Exception ex) {
ExceptionDispatchInfo.Capture(ex).Throw(); // Line 18
}
}
}
然后输出:
System.ArgumentException: Value does not fall within the expected range.
at Program.Test() in Program.cs:line 15
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Program.Test() in Program.cs:line 18
at Program.Main(String[] args) in Program.cs:line 6
如您所见,ExceptionDispatchInfo.Throw
将附加信息附加到原始异常的堆栈跟踪中,添加了重新抛出的事实,但它保留了原始行号和异常类型。有关详细信息,请参阅MSDN documentation。
答案 3 :(得分:5)
.pdb文件的日期/时间戳是否与.exe / .dll文件匹配?如果没有,可能是编译不在“调试模式”,它在每个构建上生成一个新的.pdb文件。发生异常时,pdb文件具有准确的行号。
查看编译设置以确保生成调试数据,或者如果您处于测试/生产环境中,请检查.pdb文件以确保时间戳匹配。
答案 4 :(得分:3)
C#堆栈跟踪是在投掷时生成的,而不是在创建异常时生成的。
这与Java不同,Java会在创建异常时填充堆栈跟踪。
这显然是设计上的。
答案 5 :(得分:2)