.NET:如何将异常转换为字符串?

Ian*_*oyd 51 .net c# exception tostring

抛出异常时(在IDE中进行调试时),我有机会查看异常的详细信息:

在此输入图像描述

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

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

但Visual Studio有一些魔力可以将异常复制到剪贴板:

在此输入图像描述

这给出了有用的细节:

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:
Run Code Online (Sandbox Code Playgroud)

好吧,我想要那个!

会是什么内容:

String ExceptionToString(Exception ex)
{ 
    //todo: Write useful routine
    return ex.ToString();
}
Run Code Online (Sandbox Code Playgroud)

这可以实现同样的魔力.是否在某处内置了.NET功能?是否Exception有一个秘密的地方的方法将其转换为字符串?

jas*_*son 54

ErrorCode是针对ExternalException,不ExceptionLineNumberNumber特定于SqlException,没有Exception.因此,从一般扩展方法获取这些属性的唯一方法Exception是使用反射来迭代所有公共属性.

所以你不得不说:

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);
}
Run Code Online (Sandbox Code Playgroud)

(未对恭维问题进行测试.)

.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());
}
Run Code Online (Sandbox Code Playgroud)

  • +1这是一个比我建议的覆盖`ToString()`更好的方法.我喜欢扩展方法,这似乎是一个非常好的方法,因为我们真的想要扩展基本异常类的功能. (3认同)
  • 你忘记了什么.很多时候,Exceptions有InnerExceptions,实际上几乎比你在最顶层获得的那么重要.如果InnerException不为null,您需要某种递归方法深入挖掘. (3认同)

Ger*_*art 21

我首先尝试了Jason的答案(在顶部),它的效果非常好,但我也想要:

  • 迭代循环遍历内部异常并缩进它们.
  • 忽略null属性并提高输出的可读性.
  • 它包含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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

像这样打电话:

        var builder = new StringBuilder();
        WriteExceptionDetails(exception, builder, 0);
        return builder.ToString();
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢你的解决方案@Gerben Rampaart,但它需要一些趋势.为了避免异常,我替换了`var val = propInfo.GetValue(exception);```var val = propInfo!= null?propInfo.GetValue(exception,null):null;`将其更改为私有方法并用于通过extension:`public static string ToStringAllExceptionDetails(this Exception exception){StringBuilder builderToFill = new StringBuilder(); WriteExceptionDetails(exception,builderToFill,0); return builderToFill.ToString(); }` (3认同)

Muh*_*eed 10

这个全面的答案处理写出:

  1. Data在所有异常中找到的集合属性(接受的答案不会这样做).
  2. 添加到例外的任何其他自定义属性.
  3. 递归写出InnerException(接受的答案不会这样做).
  4. 写出包含在内的异常集合AggregateException.

它还以更好的顺序写出异常的属性.它使用的是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; }
}
Run Code Online (Sandbox Code Playgroud)

热门提示 - 记录异常

大多数人都会使用此代码进行日志记录.考虑将Serilog与我的Serilog.Exceptions NuGet包一起使用,它还记录了异常的所有属性,但在大多数情况下更快,没有反射.Serilog是一个非常先进的日志框架,在撰写本文时风靡一时.

顶尖 - 人类可读堆叠痕迹

您可以使用Ben.Demystifier NuGet包为您的异常获取人类可读的堆栈跟踪,或者如果您使用Serilog,则可以使用serilog -enrichers-demystify NuGet包.如果您使用的是.NET Core 2.1,则内置此功能.


Mat*_*att 6

没有秘密的方法.您可能只是覆盖该ToString()方法并构建所需的字符串.

ErrorCodeMessage这样的东西只是可以添加到所需字符串输出的异常的属性.


更新:在重新阅读你的问题并思考这个问题之后,杰森的回答更可能是你想要的.覆盖该ToString()方法只对您创建的异常有用,而不是已经实现的异常.为了添加此功能,子类现有异常是没有意义的.


Nic*_*oul 6

对于那些不想干扰覆盖的人来说,这种简单的非侵入式方法可能就足够了:

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

它没有显示您想要的SQLException特定细节,但......