.NET:如何将exception转换为string?

当引发exception(在IDE中进行debugging时),我有机会查看exception的详细信息

在这里输入图像说明

但在代码中,如果我打电话exception.ToString()我不明白这些有用的细节:

 System.Data.SqlClient.SqlException (0x80131904): Could not find stored procedure 'FetchActiveUsers'. [...snip stack trace...] 

但是Visual Studio有一些魔力,可以将exception复制到剪贴板

在这里输入图像说明

这给出了有用的细节:

 System.Data.SqlClient.SqlException was unhandled by user code Message=Could not find stored procedure 'FetchActiveUsers'. Source=.Net SqlClient Data Provider ErrorCode=-2146232060 Class=16 LineNumber=1 Number=2812 Procedure="" Server=vader State=62 StackTrace: [...snip stack trace...] InnerException: 

那么我想要的!

什么将是的内容:

 String ExceptionToString(Exception ex) { //todo: Write useful routine return ex.ToString(); } 

那可以完成相同的魔法。 在某处是否有.NET函数? Exception是否有一个秘密的方法将其转换为string?

ErrorCode特定于ExternalException ,而不是ExceptionLineNumberNumber特定于SqlException ,而不是Exception 。 因此,从Exception的一般扩展方法获取这些属性的唯一方法是使用reflection遍历所有公共属性。

所以你不得不说:

 public static string GetExceptionDetails(this Exception exception) { var properties = exception.GetType() .GetProperties(); var fields = properties .Select(property => new { Name = property.Name, Value = property.GetValue(exception, null) }) .Select(x => String.Format( "{0} = {1}", x.Name, x.Value != null ? x.Value.ToString() : String.Empty )); return String.Join("\n", fields); } 

(没有testing过编译问题。)

.NET 2.0兼容答案:

 public static string GetExceptionDetails(this Exception exception) { PropertyInfo[] properties = exception.GetType() .GetProperties(); List<string> fields = new List<string>(); foreach(PropertyInfo property in properties) { object value = property.GetValue(exception, null); fields.Add(String.Format( "{0} = {1}", property.Name, value != null ? value.ToString() : String.Empty )); } return String.Join("\n", fields.ToArray()); } 

我首先尝试了杰森的答案(顶部),工作得很好,但我也想:

  • 迭代循环通过内部exception并缩进它们。
  • 忽略空属性并提高输出的可读性。
  • 它包含Data属性中的元数据。 (如果有)但排除Data属性本身。 (没用的)。

我现在用这个:

  public static void WriteExceptionDetails(Exception exception, StringBuilder builderToFill, int level) { var indent = new string(' ', level); if (level > 0) { builderToFill.AppendLine(indent + "=== INNER EXCEPTION ==="); } Action<string> append = (prop) => { var propInfo = exception.GetType().GetProperty(prop); var val = propInfo.GetValue(exception); if (val != null) { builderToFill.AppendFormat("{0}{1}: {2}{3}", indent, prop, val.ToString(), Environment.NewLine); } }; append("Message"); append("HResult"); append("HelpLink"); append("Source"); append("StackTrace"); append("TargetSite"); foreach (DictionaryEntry de in exception.Data) { builderToFill.AppendFormat("{0} {1} = {2}{3}", indent, de.Key, de.Value, Environment.NewLine); } if (exception.InnerException != null) { WriteExceptionDetails(exception.InnerException, builderToFill, ++level); } } 

这样的电话:

  var builder = new StringBuilder(); WriteExceptionDetails(exception, builder, 0); return builder.ToString(); 

这个综合的答案处理写出来:

  1. Data收集属性find所有例外(接受的答案不这样做)。
  2. 任何其他自定义属性添加到例外。
  3. recursion地写出InnerException (接受的答案不这样做)。
  4. 写出AggregateException包含的exceptionAggregateException

它还以较好的顺序写出exception的属性。 它使用的是C#6.0,但如果有必要,应该很容易转换为较旧的版本。

 public static class ExceptionExtensions { public static string ToDetailedString(this Exception exception) { if (exception == null) { throw new ArgumentNullException(nameof(exception)); } return ToDetailedString(exception, ExceptionOptions.Default); } public static string ToDetailedString(this Exception exception, ExceptionOptions options) { var stringBuilder = new StringBuilder(); AppendValue(stringBuilder, "Type", exception.GetType().FullName, options); foreach (PropertyInfo property in exception .GetType() .GetProperties() .OrderByDescending(x => string.Equals(x.Name, nameof(exception.Message), StringComparison.Ordinal)) .ThenByDescending(x => string.Equals(x.Name, nameof(exception.Source), StringComparison.Ordinal)) .ThenBy(x => string.Equals(x.Name, nameof(exception.InnerException), StringComparison.Ordinal)) .ThenBy(x => string.Equals(x.Name, nameof(AggregateException.InnerExceptions), StringComparison.Ordinal))) { var value = property.GetValue(exception, null); if (value == null && options.OmitNullProperties) { if (options.OmitNullProperties) { continue; } else { value = string.Empty; } } AppendValue(stringBuilder, property.Name, value, options); } return stringBuilder.ToString().TrimEnd('\r', '\n'); } private static void AppendCollection( StringBuilder stringBuilder, string propertyName, IEnumerable collection, ExceptionOptions options) { stringBuilder.AppendLine($"{options.Indent}{propertyName} ="); var innerOptions = new ExceptionOptions(options, options.CurrentIndentLevel + 1); var i = 0; foreach (var item in collection) { var innerPropertyName = $"[{i}]"; if (item is Exception) { var innerException = (Exception)item; AppendException( stringBuilder, innerPropertyName, innerException, innerOptions); } else { AppendValue( stringBuilder, innerPropertyName, item, innerOptions); } ++i; } } private static void AppendException( StringBuilder stringBuilder, string propertyName, Exception exception, ExceptionOptions options) { var innerExceptionString = ToDetailedString( exception, new ExceptionOptions(options, options.CurrentIndentLevel + 1)); stringBuilder.AppendLine($"{options.Indent}{propertyName} ="); stringBuilder.AppendLine(innerExceptionString); } private static string IndentString(string value, ExceptionOptions options) { return value.Replace(Environment.NewLine, Environment.NewLine + options.Indent); } private static void AppendValue( StringBuilder stringBuilder, string propertyName, object value, ExceptionOptions options) { if (value is DictionaryEntry) { DictionaryEntry dictionaryEntry = (DictionaryEntry)value; stringBuilder.AppendLine($"{options.Indent}{propertyName} = {dictionaryEntry.Key} : {dictionaryEntry.Value}"); } else if (value is Exception) { var innerException = (Exception)value; AppendException( stringBuilder, propertyName, innerException, options); } else if (value is IEnumerable && !(value is string)) { var collection = (IEnumerable)value; if (collection.GetEnumerator().MoveNext()) { AppendCollection( stringBuilder, propertyName, collection, options); } } else { stringBuilder.AppendLine($"{options.Indent}{propertyName} = {value}"); } } } public struct ExceptionOptions { public static readonly ExceptionOptions Default = new ExceptionOptions() { CurrentIndentLevel = 0, IndentSpaces = 4, OmitNullProperties = true }; internal ExceptionOptions(ExceptionOptions options, int currentIndent) { this.CurrentIndentLevel = currentIndent; this.IndentSpaces = options.IndentSpaces; this.OmitNullProperties = options.OmitNullProperties; } internal string Indent { get { return new string(' ', this.IndentSpaces * this.CurrentIndentLevel); } } internal int CurrentIndentLevel { get; set; } public int IndentSpaces { get; set; } public bool OmitNullProperties { get; set; } } 

重要提示 – loggingexception

大多数人将使用此代码进行日志logging。 考虑使用Serilog与我的Serilog.Exceptions NuGet软件包,它也logging一个exception的所有属性,但更快,并在大多数情况下没有反映。 Serilog是一个非常先进的日志logging框架,在写作的时候风靡一时。

顶端提示 – 人类可读栈跟踪

您可以使用Ben.Demystifier NuGet软件包为您的例外或者serilog-enrichers(如果您使用Serilog的话,可以解开 NuGet软件包)获取人类可读的堆栈跟踪。

没有秘密的方法。 您可能可以重写ToString()方法并构build所需的string。

诸如ErrorCodeMessage之类的东西只是可以添加到所需string输出的exception的属性。


更新:在重新阅读你的问题并思考更多这个问题之后,Jason的回答更可能是你想要的。 重写ToString()方法只会对你创build的exception有帮助,还没有实现。 为了增加这个function,子类现有的exception是没有意义的。

为了向用户显示一些细节,你应该使用ex.Message 。 为了向开发者显示,你可能需要ex.Messageex.StackTrace

没有“秘密”的方法,你可以考虑消息属性是最适合用户友好的消息。

另外要小心,在某些情况下,你可能会有exception的内部exception,你可以捕获这些exception,这对logging也很有用。

您可能必须通过连接您感兴趣的各个字段来手动构build该string。

每个左侧名称都是exception中的属性。 如果你想显示消息字段,你可以这样做

 return ex.Message; 

很简单。 同样, StackTrace可以显示为下面的链接。

StackTrace的完整示例: http : //msdn.microsoft.com/zh-cn/library/system.exception.stacktrace.aspx

和Exception类: http : //msdn.microsoft.com/en-us/library/system.exception.aspx

对于不想凌驾于压倒一切的人,这种简单的非侵入式方法可能就足够了:

  public static string GetExceptionDetails(Exception exception) { return "Exception: " + exception.GetType() + "\r\nInnerException: " + exception.InnerException + "\r\nMessage: " + exception.Message + "\r\nStackTrace: " + exception.StackTrace; } 

它不显示您想要的SQLException特定的细节,但…

如果在Exception对象上调用ToString,则会得到消息附加的类名称,然后是内部exception,然后是堆栈跟踪。

 className + message + InnerException + stackTrace 

鉴于此,只有在InnerException和StackTrace不为空时才会添加它们。 此外,您在截图中提到的字段不是标准的Exception类的一部分。 是的,exception提供了一个名为“数据”的公共属性,其中包含有关该exception的其他用户定义信息。

在visual studio中,可以通过debugging器可视化工具输出这些信息。

我认为,因为可以编写自己的debugging器可视化器: http : //msdn.microsoft.com/en-us/library/e2zc529c.aspx

理论上讲,如果你可以对内置的debugging器可视化器进行逆向工程,那么你可以使用相同的function。

编辑:

这里是一个关于debugging器可视化器保存位置的文章: 我在哪里可以findMicrosoft.VisualStudio.DebuggerVisualizers?

您可以将其用于您自己的目的。