【问题标题】:Preserving original StackTrace/LineNumbers in .NET Exceptions在 .NET 异常中保留原始 StackTrace/LineNumbers
【发布时间】:2010-12-07 18:58:46
【问题描述】:

了解throw exthrow的区别,为什么在这个例子中保留了原始的StackTrace:

    static void Main(string[] args)
    {
        try
        {
            LongFaultyMethod();
        }
        catch (System.Exception ex)
        {
            Console.WriteLine(ex.StackTrace);
        }
    }

    static void LongFaultyMethod()
    {
        try
        {
            int x = 20;
            SomethingThatThrowsException(x);
        }
        catch (Exception)
        {
            throw;
        }
    }

    static void SomethingThatThrowsException(int x)
    {
        int y = x / (x - x);
    }

但不是在这个:

    static void Main(string[] args)
    {
        try
        {
            LongFaultyMethod();
        }
        catch (System.Exception ex)
        {
            Console.WriteLine(ex.StackTrace);
        }
    }

    static void LongFaultyMethod()
    {
        try
        {
            int x = 20;
            int y = x / (x - 20);
        }
        catch (Exception)
        {
            throw;
        }
    }

第二种情况是产生与 throw ex 相同的输出吗?

在这两种情况下,人们都希望看到初始化 y 的行号。

【问题讨论】:

    标签: .net exception line-numbers


    【解决方案1】:

    我不确定此限制是否在 C# 语言、CLI 或 Microsoft 实现中,但您的第二个示例是需要显式调用 Exception.InternalPreserveStackTrace 的情况,如以下帖子中所述.由于这个方法是internal,所以一般要通过反射调用。通过为调用创建Action<Exception> 几乎可以完全缓解其中涉及的性能问题,如本答案末尾所示。

    参考:Rethrowing exceptions and preserving the full call stack trace

    编辑:在重新检查 ECMA-335 Partition I §12.4.2(异常处理)和 Partition III §4.24(重新抛出)后,我现在认为您看到的行为是语义错误CLR(微软的 CLI 实现)。对该行为的唯一具体引用是“A rethrow 不会更改对象中的堆栈跟踪”。在此处描述的情况下,重新抛出实际上改变了堆栈跟踪,使PreserveStackTrace hack 成为已知 CLR 缺陷的解决方法。

    static void LongFaultyMethod() 
    { 
        try 
        { 
            int x = 20; 
            int y = x / (x - 20); 
        } 
        catch (Exception ex) 
        { 
            PreserveStackTrace(ex); // <-- add this line
            throw; 
        } 
    } 
    

    PreserveStackTrace 这是对该博客条目的优化:

    private static readonly Action<Exception> _internalPreserveStackTrace =
        (Action<Exception>)Delegate.CreateDelegate(
            typeof(Action<Exception>),
            typeof(Exception).GetMethod(
                "InternalPreserveStackTrace",
                BindingFlags.Instance | BindingFlags.NonPublic));
    
    public static void PreserveStackTrace(Exception e)
    {
        _internalPreserveStackTrace(e);
    }
    

    【讨论】:

      【解决方案2】:

      因为在第二个示例中,您正在从同一方法重新抛出异常。首先它是从不同的方法抛出的,这就是为什么。在一个方法范围内,堆栈跟踪只能是一个。

      按照以下方式操作,最好的方法是始终将异常包装在新异常中,以便您看到异常深度。

      "如果重新抛出已在 相同的方法(异常堆栈跟踪 只有一个行号信息 每个方法,你永远不会看到堆栈跟踪 在方法 A 中的第 2 行 抛出异常,然后在同一个 方法A,从行重新抛出 数字 17,它只包含最后一个 异常所在的行号 重新抛出”

      try        
      {            
         int x = 20;            
         int y = x / (x - 20);        
      }        
      catch (Exception ex)        
      {            
         // do something here.. like log or something
         throw new Exception("Internal Exception", ex);        
      }
      

      我对这么多 cmets 没有阅读我的评论感到惊讶!我在评论中写道,您可能应该安全地记录这个,有多种原因,如果顶级代码吃掉异常并且您不知道抛出了哪个和哪里的异常,日志记录可以帮助您交叉异常! !

      如果您不需要记录,则不要捕获异常。

      【讨论】:

      • 我知道这是一种可行的解决方法,但我仍然不明白为什么第二个示例中的 rethrow 不应该有正确的行号。
      • @Akash Kava:你有参考吗?我不相信你完全理解 OP 的问题。
      • @awe:我强烈反对。如果你没有任何有用的信息要添加到外部异常中,那么你应该重新抛出异常而不是包装它。
      • 重新抛出根本不会保留行号,重新抛出会显示第一个堆栈项的行是“重新抛出”的行,如果你的异常被捕获并在同一个函数中重新抛出,你会永远不知道异常是在哪里引发的,让我们举个例子,XAML 使用它,ASP.NET 使用它,我不认为微软人使用内部异常而不担心性能。
      • @280Z:两者都不是。如果您没有要添加的有用信息,则根本不要捕获异常。
      猜你喜欢
      • 1970-01-01
      • 2023-01-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-04
      • 2015-10-26
      • 2010-09-28
      相关资源
      最近更新 更多