【问题标题】:How can I add a message to an exception without losing any information in C#?如何在不丢失 C# 中的任何信息的情况下向异常添加消息?
【发布时间】:2013-01-19 00:55:30
【问题描述】:

我有以下代码:

catch(Exception ex)
{
    throw new FatalException("An error occurred while trying to load the XSLT file.", ex);
}

不幸的是,这只是吞噬了异常。我可以通过执行以下操作来解决此问题:

catch(Exception ex)
{
    throw;
}

但我仍然希望包含自定义消息以获取有关事件记录的帮助。

如何在不丢失任何信息的情况下将此消息添加到异常中? (堆栈跟踪/调试符号等)

【问题讨论】:

  • 将原始异常添加为InnerException FatalException?
  • throw new FatalException("message", ex)的末尾。
  • @Mark 您可以这样做来添加一条可以显示给用户的用户友好消息,然后自己进行日志记录。如果您从 ApplicationException 派生 FatalException,您将能够轻松识别您添加消息的异常与代码中其他任何地方抛出的异常。
  • 是的,这主要是为了有一个用户友好的消息(用于记录目的),但将原始信息保留在 Exception 中(用于调试目的)。
  • @krillgar 从ApplicationException 派生是过去推荐的做法,但现在不再适用。那是因为一些框架异常实际上也派生自该类。

标签: c# exception exception-handling


【解决方案1】:

如果您只需要向原始异常添加信息,例如用户可读的消息或有助于您跟踪错误但对最终用户无用的特定详细信息,您可以利用异常的Data 属性,它是一个键/值对字典。

我们广泛使用它来记录信息,例如正在执行的报告或正在处理的文件,以便操作可以确定错误发生时的确切情况。用户不需要此详细信息,因为他们直接处理失败的原因。

您还可以使用它来传递对用户有意义的纯文本消息。唯一的问题是您必须在日志框架或最终用户界面中执行一些额外的工作才能提取数据并使其对消费者有用。

例如,你可以这样做:

catch (Exception ex)
{
    ex.Data.Add("UserMessage", "An error occurred while trying to load the XSLT file.");
    throw;
}

然后在客户端代码中,您可以测试一下 UserMessage 是否存在,如果存在,则将其呈现给用户而不是 Exception:

catch (Exception ex)
{
    if (ex.Data.Contains("UserMessage"))
    {
        MessageBox.Show(ex.Data["UserMessage"].ToString());
    }
    else
    {
        MessageBox.Show(ex.Message);
    }
}

【讨论】:

  • 更好的是ex.Data["UserMessage"] += "An error occurred...";,这样如果一些较低级别的代码已经添加了一条消息,它会将额外的文本附加到它上面而不是抛出一个ArgumentException
  • 太棒了,谢谢!这些额外的数据也会自动添加到 .NET 跟踪日志中,如果您在 try/catch 链的整个过程中使用唯一键和描述性消息,则无需花费任何时间来查找错误原因。
  • 这样做有什么好处,而不是根据接受的答案抛出一个新的异常并将原始异常作为 innerException 传递?
  • @ajbeaven:有很多好处: 1) 抛出和捕获异常在应用程序性能方面的成本可能非常高; 2)将结构化数据添加到异常意味着您可以将其解析出来并根据结构化数据执行额外的工作、路由或报告。如果要将信息嵌入到异常中,则必须解析内部异常以确定它是否包含有用的信息; 3)许多人将异常消息发送给用户或记录并在异常中封装附加信息会导致错误消息
  • 我可以理解1。我看不到2的好处,只是解析会不同。无论如何,任何好的日志记录或异常处理系统都已经递归地打印内部异常。关于 3 - 我认为这将是使用 innerException 方法的一个优点 - 你真的想发送“NullReferenceException:对象引用未设置为对象的实例”。给客户?
【解决方案2】:

原来的异常仍然存在。

当您进行异常记录时,您收到的异常将是您使用消息生成的 FatalException。原始异常位于ex.InnerException。您可以继续循环 InnerException 直到它为 null 以获取所有 Stack Trace 信息等。

【讨论】:

    【解决方案3】:

    简而言之,不要。

    我相信您可以通过一些反思找到解决此问题的方法,但我会强烈提醒您不要这样做。它违背了 .NET 中异常的原始设计。异常不仅有助于记录,它们还提供有关应用程序失败的原始原因的信息。

    通常首选使用第一个选项,因为它维护原始异常的堆栈跟踪,但允许您通过将其包装在单独的异常中来提供附加信息。在我自己的代码中,每当我记录异常时,我的记录函数都会通过 InnerException 属性进行递归,以找到有关错误的所有可能有用信息。

    【讨论】:

    • “记录”可能不是用于此的最佳术语。如果您正在做一些远程操作,您可以在您无权访问任何日志记录时发送一封电子邮件,说明特定类型的异常。
    【解决方案4】:

    以防万一有人需要一个好的答案。关键是要使用 AppDomain.CurrentDomain.FirstChanceException

    您可以使用 IDisposable 创建一个自定义对象以将所有信息放入其中。如果发生异常,则 FirstChanceException 处理程序会获取该信息并填充 Exception.Data。

    使用本地线程存储使其线程安全。然后捕获它的代码将获取数据并记录下来。

    例子:

    using(MyCustomMessage.EnterToLocalStorage("Info for logging"") ) 
    { 
    ...code 
    ...exception thrown
    .... FirstChanceException examines local thread storage and get's "info for logging" and puts into Exception.Data. 
    } 
    //Dispose is called and all messages that were put into LocalStorage are removed. 
    //So if exception was not thrown before then it like nothing happened.
    

    Google AsyncDiagnosticStack 就是一个很好的例子。 https://github.com/StephenCleary/AsyncDiagnostics/blob/master/src/Nito.AsyncEx.AsyncDiagnostics/AsyncDiagnosticStack.cs

    【讨论】:

      猜你喜欢
      • 2011-02-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-28
      • 1970-01-01
      • 1970-01-01
      • 2011-08-29
      • 1970-01-01
      相关资源
      最近更新 更多