【问题标题】:Find where rethrown exception was originally thrown using Visual Studio C# debugger?使用 Visual Studio C# 调试器查找最初引发重新抛出异常的位置?
【发布时间】:2012-02-09 20:35:13
【问题描述】:

重新抛出异常时通常的建议是使用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.
            }
        }
    }
}

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

【问题讨论】:

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


【解决方案1】:

您最好的选择是让 Visual Studio 中断原始异常,而不是从堆栈跟踪中导航回它。为此:

1) 点击“调试”菜单项 2) 点击“例外...” 3) 选择“通用语言运行时异常”-“抛出”

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

看图:

【讨论】:

  • 即使您不使用throw;(而是使用throw ex;),这也适用,对吧?那么用throw;代替throw ex;有什么好处呢?
  • 调用堆栈窗口将不包含发生异常的点。 Exception.StackTrace 属性包含保留的堆栈跟踪。按照此答案中的步骤或阅读异常的 StackTrace 属性以确定异常的来源。
  • @Jon-Eric 如果您使用throw ex; 而不是throw;,Exception 上的 StackTrace 属性将不会保留堆栈跟踪。始终使用throw; 或包装异常重新抛出,即throw new ApplicationException("My exception explanation.", ex);
  • @James 谢谢。那讲得通。 我希望可视化调试器能更好地帮助我利用 StackTrace 属性。我找到了 this has been explained before
  • 我使用 Ctrl + Alt + E 到达这里。我每天多次打开/关闭此开关。
【解决方案2】:

如果您运行的是 Visual Studio 2010 Ultimate,use IntelliTrace

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

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

【讨论】:

  • 非常感谢乔恩-埃里克
【解决方案3】:

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

我发现它在处理 AppDomainWPF Dispatcher 上的未处理异常时非常有用,因为 Visual Studio 总是为时已晚。

根据Code Project 上的一篇文章,我对其进行了修改,将其作为单个文本块输出到 控制台 - 而不是逐行 - 这是我记录所必需的还写信给控制台

用法

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

}

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

扩展方法

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

【讨论】:

    【解决方案4】:

    您可以使用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.
                }
            }
        }
    }
    

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        猜你喜欢
        • 2012-01-16
        • 1970-01-01
        • 2013-07-28
        • 1970-01-01
        • 2011-12-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多