查找最初使用Visual Studio C#debugger抛出rethrown异常的位置?

时间:2012-02-09 20:35:14

标签: c# visual-studio debugging exception-handling

重新抛出异常时,通常的建议是使用throw;语句,以便保留原始堆栈跟踪。 (Example

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

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.
            }
        }
    }
}

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

5 个答案:

答案 0 :(得分:9)

您最好的选择是让Visual Studio打破原始异常,而不是从堆栈跟踪导航回来。要做到这一点:

1)单击“Debug”菜单项 2)点击“例外...” 3)选择“Common Language Runtime Exceptions” - “Thrown”

使用这种方法,如果抛出许多异常,您可能会得到比您真正想要的更多。您可以通过展开树列表来过滤它中断的异常。

见图:

enter image description here

答案 1 :(得分:7)

如果您正在运行Visual Studio 2010 Ultimate,use IntelliTrace

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

(取自Chris Schmich's answer to a similar question。)

答案 2 :(得分:1)

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

我发现在处理 AppDomain WPF Dispatcher 上的未处理异常时非常有用,因为Visual Studio总是打破得太晚。

基于Code Project上的一篇文章,我修改了它作为单个文本块输出到 Console - 而不是逐行 - 这是必要的我记录还写入控制台

<强>用法

public void ReportException(Exception exception)
{
    if (Debugger.IsAttached)
    {
        DebugHelper.PrintExceptionToConsole(exception);
        Debugger.Break();
    }

    // ...

}

<强>来源

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();
    }

}

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

扩展方法

/// <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();
}

答案 3 :(得分:1)

您可以使用DebuggerNonUserCode属性。

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

示例如下:

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.
            }
        }
    }
}

答案 4 :(得分:0)

顺便说一句,在vb.net中,可以在某些情况下使用异常过滤器,其中一个人知道一个人真的不想捕获异常 - 只是发现它发生了。如果代码是在vb.net中编写的并且使用过滤器来捕获异常(可能在'finally'块中执行输出),则不会出现“catch和rethrow” - 光标会跳转到源代码原始异常(作为奖励,vs会在任何堆栈展开之前中断程序流程)。请注意,每次抛出特定异常时都可以选择使用vs trap,无论是否会被捕获,但有时候只会抛出一些抛出异常的地方而不是所有异常。