(IDEでのデバッグ中に)例外がスローされると、例外の詳細を表示する機会があります。
しかし、コードでexception.ToString()
を呼び出すと、これらの有用な詳細が表示されません。
System.Data.SqlClient.SqlException (0x80131904): Could not find stored procedure 'FetchActiveUsers'.
[...snip stack trace...]
しかし、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:
まあそれが欲しい!
内容は次のとおりです。
String ExceptionToString(Exception ex)
{
//todo: Write useful routine
return ex.ToString();
}
それは同じ魔法を達成できます。どこかに.NET関数が組み込まれていますか? Exception
には、文字列に変換する秘密のメソッドがどこかにありますか?
ErrorCode
はExternalException
ではなく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());
}
私は最初にジェイソンの答えを試してみました(上部)、それはかなりうまくいきましたが、私も欲しかったです:
私は今これを使用します:
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
コレクションプロパティ(受け入れられた答えはこれを行いません)。InnerException
を再帰的に書き出します(受け入れられた答えはこれを行いません)。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を使用している場合、この機能が組み込まれています。
秘密の方法はありません。おそらくToString()
メソッドをオーバーライドして、必要な文字列を作成できます。
ErrorCodeやMessageのようなものは、目的の文字列出力に追加できる例外の単なるプロパティです。
更新:質問を読み直して、これについてさらに考えた後、ジェイソンの answer は、おそらくあなたが望んでいるものです。 ToString()
メソッドのオーバーライドは、既に実装されている例外ではなく、作成した例外に対してのみ役立ちます。この機能を追加するためだけに既存の例外をサブクラス化することは意味がありません。
オーバーライドを台無しにしたくない人にとっては、この単純で非侵入的な方法で十分かもしれません。
public static string GetExceptionDetails(Exception exception)
{
return "Exception: " + exception.GetType()
+ "\r\nInnerException: " + exception.InnerException
+ "\r\nMessage: " + exception.Message
+ "\r\nStackTrace: " + exception.StackTrace;
}
ただし、必要なSQLException固有の詳細は表示されません...
ユーザーに詳細を表示するには、ex.Message
を使用する必要があります。開発者に表示するには、おそらくex.Message
とex.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
おそらく、興味のあるさまざまなフィールドを連結して、その文字列を手動で作成する必要があります。
Visual Studioでは、そのような情報はデバッガビジュアライザーによって出力できます。
独自のデバッガービジュアライザーを作成できるため、次のように思います。 http://msdn.Microsoft.com/en-us/library/e2zc529c.aspx
理論的には、組み込みデバッガビジュアライザーの例外をリバースエンジニアリングできる場合(例外が格納されている場所を解決できる場合)、同じ機能を使用できます。
編集:
デバッガービジュアライザーの保存場所に関する投稿は次のとおりです。 Microsoft.VisualStudio.DebuggerVisualizersはどこにありますか?
独自の目的に使用できる場合があります。
ExceptionオブジェクトでToStringを呼び出すと、メッセージにクラス名が追加され、その後に内部例外とスタックトレースが続きます。
className + message + InnerException + stackTrace
そのため、InnerExceptionとStackTraceは、nullでない場合にのみ追加されます。また、スクリーンショットで言及したフィールドは、標準の例外クラスの一部ではありません。はい、例外は「Data」というパブリックプロパティを提供します。これには、例外に関する追加のユーザー定義情報が含まれます。