.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
,而不是Exception
, LineNumber
和Number
特定于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();
这个综合的答案处理写出来:
-
Data
收集属性find所有例外(接受的答案不这样做)。 - 任何其他自定义属性添加到例外。
- recursion地写出
InnerException
(接受的答案不这样做)。 - 写出
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。
诸如ErrorCode和Message之类的东西只是可以添加到所需string输出的exception的属性。
更新:在重新阅读你的问题并思考更多这个问题之后,Jason的回答更可能是你想要的。 重写ToString()
方法只会对你创build的exception有帮助,还没有实现。 为了增加这个function,子类现有的exception是没有意义的。
为了向用户显示一些细节,你应该使用ex.Message
。 为了向开发者显示,你可能需要ex.Message
和ex.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?
您可以将其用于您自己的目的。