web-dev-qa-db-ja.com

.NET:例外を文字列に変換する方法は?

(IDEでのデバッグ中に)例外がスローされると、例外の詳細を表示する機会があります。

enter image description here

しかし、コードでexception.ToString()を呼び出すと、これらの有用な詳細が表示されません。

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

しかし、Visual Studioにはいくつかの魔法があります例外をクリップボードにコピー

enter image description here

これは有用な詳細を提供します:

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には、文字列に変換する秘密のメソッドがどこかにありますか?

50
Ian Boyd

ErrorCodeExternalExceptionではなくExceptionに固有であり、LineNumberおよびNumberおよびSqlExceptionは_ [ではなくExceptionに固有です$ var] _。したがって、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);
}

(コンパイルの問題についてはテストされていません。)

.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());
}
54
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);
        }
    }

このように呼び出します:

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

この包括的な回答は、書き出しを処理します。

  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; }
}

先端のヒント-例外のログ

ほとんどの人は、このコードをログに使用します。 Serilog with Serilog.Exceptions NuGetパッケージを使用することを検討してください。NuGetパッケージは、例外のすべてのプロパティも記録しますが、ほとんどの場合、より速く、反映しません。 Serilogは非常に高度なロギングフレームワークであり、執筆時点で大流行しています。

ヒント-人間が読めるスタックトレース

Ben.Demystifier NuGetパッケージを使用して、例外の人間が読めるスタックトレースを取得するか、Serilogを使用している場合は serilog-enrichers-demystify NuGetパッケージを使用できます。 .NET Core 2.1を使用している場合、この機能が組み込まれています。

10

秘密の方法はありません。おそらくToString()メソッドをオーバーライドして、必要な文字列を作成できます。

ErrorCodeMessageのようなものは、目的の文字列出力に追加できる例外の単なるプロパティです。


更新:質問を読み直して、これについてさらに考えた後、ジェイソンの answer は、おそらくあなたが望んでいるものです。 ToString()メソッドのオーバーライドは、既に実装されている例外ではなく、作成した例外に対してのみ役立ちます。この機能を追加するためだけに既存の例外をサブクラス化することは意味がありません。

6
Matt

オーバーライドを台無しにしたくない人にとっては、この単純で非侵入的な方法で十分かもしれません。

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

ただし、必要なSQLException固有の詳細は表示されません...

6
Nicolas Raoul

ユーザーに詳細を表示するには、ex.Messageを使用する必要があります。開発者に表示するには、おそらくex.Messageex.StackTraceが必要です。

「秘密」の方法はありません。Messageプロパティはユーザーフレンドリーなメッセージに最適であると考えることができます。

また、場合によっては、キャッチする例外で内部例外が発生する可能性があることに注意してください。

左側の各名前は、例外のプロパティです。 Messageフィールドを表示したい場合は、

return ex.Message;

ものすごく単純。同様に、StackTraceは以下のリンクとして表示できます。

StackTraceの完全な例: http://msdn.Microsoft.com/en-us/library/system.exception.stacktrace.aspx

および例外クラス: http://msdn.Microsoft.com/en-us/library/system.exception.aspx

2
Youngjae

おそらく、興味のあるさまざまなフィールドを連結して、その文字列を手動で作成する必要があります。

2
Tudor

Visual Studioでは、そのような情報はデバッガビジュアライザーによって出力できます。

独自のデバッガービジュアライザーを作成できるため、次のように思います。 http://msdn.Microsoft.com/en-us/library/e2zc529c.aspx

理論的には、組み込みデバッガビジュアライザーの例外をリバースエンジニアリングできる場合(例外が格納されている場所を解決できる場合)、同じ機能を使用できます。

編集:

デバッガービジュアライザーの保存場所に関する投稿は次のとおりです。 Microsoft.VisualStudio.DebuggerVisualizersはどこにありますか?

独自の目的に使用できる場合があります。

0
Alex KeySmith

ExceptionオブジェクトでToStringを呼び出すと、メッセージにクラス名が追加され、その後に内部例外とスタックトレースが続きます。

className + message + InnerException + stackTrace

そのため、InnerExceptionとStackTraceは、nullでない場合にのみ追加されます。また、スクリーンショットで言及したフィールドは、標準の例外クラスの一部ではありません。はい、例外は「Data」というパブリックプロパティを提供します。これには、例外に関する追加のユーザー定義情報が含まれます。

0
Pawan Mishra