使用Visual Studio C#调试器查找最初引发rethrown异常的位置?

Find where rethrown exception was originally thrown using Visual Studio C# debugger?

当重新引发异常时,通常的建议是使用throw;语句,以便保留原始堆栈跟踪。(例)

但是,当我尝试这个简单的示例时,Visual Studio调试器不显示原始堆栈跟踪。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
namespace ExceptionTest
{
    class Program
    {
        static void ThrowException()
        {
            throw new System.Exception();  // The line that I WANT the debugger to show.
        }

        static void Main(string[] args)
        {
            try
            {
                ThrowException();
            }
            catch (System.Exception)
            {
                System.Console.WriteLine("An exception was thrown.");

                throw;  // The line that the debugger ACTUALLY shows.
            }
        }
    }
}

如何使用调试器查找异常的原始源?


最好的选择是要求Visual Studio打破原始异常,而不是从堆栈跟踪导航回它。这样做:

1)单击"调试"菜单项2)单击"例外…"3)选择"公共语言运行时异常"-"引发"

使用这种方法,如果抛出许多异常,您可能会得到比实际需要更多的结果。您可以通过展开树列表来筛选它破坏的异常。

见图像:

enter image description here


如果运行的是Visual Studio 2010旗舰版,请使用IntelliTrace。

它保存所有抛出异常的记录,并允许您"及时调试",以便在每次抛出时查看参数、线程和变量。

(摘自克里斯·施密奇对类似问题的回答。)


您可以使用DebuggerNonUserCode属性。

请参阅http://blogs.msdn.com/b/jmstall/archive/2007/02/12/making-catch-rethrow-more-debuggable.aspx

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
namespace ExceptionTest
{
    class Program
    {
        static void ThrowException()
        {
            throw new System.Exception();  // The line that I WANT the debugger to show.
        }

        [DebuggerNonUserCode()]
        static void Main(string[] args)
        {
            try
            {
                ThrowException();
            }
            catch (System.Exception)
            {
                System.Console.WriteLine("An exception was thrown.");

                throw;  // The line that the debugger ACTUALLY shows.
            }
        }
    }
}

我找到的最佳解决方案是将Exception调用堆栈写入Debug.Console,然后让Visual Studio中的内置代码行分析器提供导航。

我发现它在处理AppDomain和WPF调度程序上未处理的异常时非常有用,因为Visual Studio总是太迟中断。

基于一篇关于代码项目的文章,我修改了它,它以单个文本块(而不是一行一行)的形式输出到控制台,这是必需的,我还将日志记录写入控制台。

用法

1
2
3
4
5
6
7
8
9
10
11
public void ReportException(Exception exception)
{
    if (Debugger.IsAttached)
    {
        DebugHelper.PrintExceptionToConsole(exception);
        Debugger.Break();
    }

    // ...

}

来源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public static class DebugHelper
{
    // Original idea taken from the CodeProject article
    // http://www.codeproject.com/Articles/21400/Navigating-Exception-Backtraces-in-Visual-Studio

    private static readonly string StarSeparator = new String('*', 80);
    private static readonly string DashSeparator = new String('-', 80);
    private const string TabString ="  ";

    /// <summary>
    /// Prints the exception using a format recognized by the Visual Studio console parser.
    /// Allows for quick navigation of exception call stack.
    /// </summary>
    /// <param name="exception">The exception.</param>
    public static void PrintExceptionToConsole(Exception exception)
    {
        using (var indentedTextWriter = new IndentedTextWriter(Console.Out, TabString))
        {                
            var indentLevel = 0;
            while (exception != null)
            {
                indentedTextWriter.Indent = indentLevel;
                indentedTextWriter.Write(FormatExceptionForDebugLineParser(exception));
                exception = exception.InnerException;
                indentLevel++;
            }
        }
    }

    private static string FormatExceptionForDebugLineParser(Exception exception)
    {
        StringBuilder result = new StringBuilder();

        result.AppendLine(StarSeparator);
        result.AppendLineFormat("  {0}: "{1}"", exception.GetType().Name, exception.Message);
        result.AppendLine(DashSeparator);

        // Split lines into method info and filename / line number
        string[] lines = exception.StackTrace.Split(new string[] {" at" }, StringSplitOptions.RemoveEmptyEntries)
                                                .Select(x => x.Trim())
                                                .Where(x => !String.IsNullOrEmpty(x))
                                                .ToArray();

        foreach (var line in lines)
        {
            string[] parts = line.Split(new string[] {" in" }, StringSplitOptions.RemoveEmptyEntries);
            string methodInfo = parts[0];
            if (parts.Length == 2)
            {
                string[] subparts = parts[1].Split(new string[] {":line" }, StringSplitOptions.RemoveEmptyEntries);
                result.AppendLineFormat("  {0}({1},1): {2}", subparts[0], Int32.Parse(subparts[1]), methodInfo);
            }
            else
                result.AppendLineFormat("  {0}", methodInfo);
        }

        result.AppendLine(StarSeparator);

        return result.ToString();
    }

}

要使用上述方法,您还需要下面的扩展方法,并为IndentedTextWriter添加System.CodeDom.Compiler名称空间。

扩展方法

1
2
3
4
5
6
7
8
9
10
11
/// <summary>
/// Appends the string returned by processing a composite format string followed by the default line terminator.
/// </summary>
/// <param name="sb">The StringBuilder.</param>
/// <param name="format">The format.</param>
/// <param name="args">The args.</param>
public static void AppendLineFormat(this StringBuilder sb, string format, params object[] args)
{
    sb.AppendFormat(format, args);
    sb.AppendLine();
}

顺便说一句,在vb.net中,我们可以在这样的情况下使用异常过滤器,在这种情况下,我们知道自己对捕获异常并不真正感兴趣——只是发现它发生了。如果代码是用vb.net编写的,并使用过滤器捕获异常(可能是在"finally"块中执行输出本身),则不会出现"catch and rethrow"--光标将跳转到原始异常的源(作为额外的好处,vs将在任何堆栈展开之前中断程序流)。请注意,每次抛出一个特定的异常时,无论它是否会被捕获,都可以选择使用vs陷阱,但有时,人们只对抛出异常的某些位置感兴趣,而不是对所有位置感兴趣。