InnerException(s)からすべてのメッセージを取得しますか?


92

スローされた例外のInnerExceptionのすべてのレベルに移動するためのLINQスタイルの「ショートハンド」コードを作成する方法はありますか?拡張関数(以下のように)を呼び出したり、Exceptionクラスを継承したりするのではなく、その場で記述したいと思います。

static class Extensions
{
    public static string GetaAllMessages(this Exception exp)
    {
        string message = string.Empty;
        Exception innerException = exp;

        do
        {
            message = message + (string.IsNullOrEmpty(innerException.Message) ? string.Empty : innerException.Message);
            innerException = innerException.InnerException;
        }
        while (innerException != null);

        return message;
    }
}; 

2
拡張メソッド以外のものを使用したい理由をお聞きしますか?あなたのコードは私には問題なく見え、あなたのコードのどこでも再利用できます。
ken2k 2012

@ ken2k:今のようにメッセージを作成したくないのですが...
Jeff Mercado

1
@JeffMercadoはい、しかし「拡張メソッド」の概念の問題は何ですか?
ken2k 2012

@ ken2k:正直なところ、私はあなたの質問を本当に理解していません...あなたはコードに欠陥があると「見栄えがする」と言っただけです。
ジェフメルカード2012

1
AggregateException動作が少し異なることに注意してください。InnerExceptions代わりに、プロパティをウォークスルーする必要があります。ここで便利な拡張メソッドを提供しました:stackoverflow.com/a/52042708/661933両方のケースをカバーします。
nawfal 2018

回答:


92

残念ながら、LINQは階層構造を処理できるメソッドを提供せず、コレクションのみを提供します。

私は実際にこれを行うのに役立ついくつかの拡張メソッドを持っています。正確なコードは手元にありませんが、次のようなものです。

// all error checking left out for brevity

// a.k.a., linked list style enumerator
public static IEnumerable<TSource> FromHierarchy<TSource>(
    this TSource source,
    Func<TSource, TSource> nextItem,
    Func<TSource, bool> canContinue)
{
    for (var current = source; canContinue(current); current = nextItem(current))
    {
        yield return current;
    }
}

public static IEnumerable<TSource> FromHierarchy<TSource>(
    this TSource source,
    Func<TSource, TSource> nextItem)
    where TSource : class
{
    return FromHierarchy(source, nextItem, s => s != null);
}

次に、この場合、これを実行して、例外を列挙することができます。

public static string GetaAllMessages(this Exception exception)
{
    var messages = exception.FromHierarchy(ex => ex.InnerException)
        .Select(ex => ex.Message);
    return String.Join(Environment.NewLine, messages);
}

81

このような意味ですか?

public static class Extensions
{
    public static IEnumerable<Exception> GetInnerExceptions(this Exception ex)
    {
        if (ex == null)
        {
            throw new ArgumentNullException("ex");
        }

        var innerException = ex;
        do
        {
            yield return innerException;
            innerException = innerException.InnerException;
        }
        while (innerException != null);
    }
}

このようにして、次のように、例外階層全体でLINQを実行できます。

exception.GetInnerExceptions().Where(e => e.Message == "Oops!");

2
提案されたソリューションよりもはるかにクリーン
ライス

1
@Rice提案された解決策は、複数の平坦化シナリオに対するこの問題の一般化であることに注意してください。それがより複雑であるという事実が予想されます。
julealgon

31

このコードはどうですか:

private static string GetExceptionMessages(this Exception e, string msgs = "")
{
  if (e == null) return string.Empty;
  if (msgs == "") msgs = e.Message;
  if (e.InnerException != null)
    msgs += "\r\nInnerException: " + GetExceptionMessages(e.InnerException);
  return msgs;
}

使用法:

Console.WriteLine(e.GetExceptionMessages())

出力の例:

メッセージを受け入れることができるエンドポイントがhttp://nnn.mmm.kkk.ppp:8000 / routingservice / routerでリッスンしていませんでした。これは多くの場合、誤ったアドレスまたはSOAPアクションが原因で発生します。詳細については、InnerExceptionが存在する場合は、を参照してください。

InnerException:リモートサーバーに接続できません

InnerException:ターゲットマシンが積極的に拒否したため、接続を確立できませんでした127.0.0.1:8000


3
StringBuilderここでの使用を本当に検討する必要があります。また、IMO拡張メソッドはNullReferenceException、null参照で呼び出されたときにスローする必要があります。
dstarkowski 2016年

27

これは明らかですが、すべてではないかもしれません。

exc.ToString();

これはすべての内部例外を通過し、すべてのメッセージを返しますが、スタックトレースなども一緒に返します。


3
ToStringで誇張されたすべての完全なスタックトレースを使用して問題がなければ、それで問題ありません。これは、メッセージがユーザーに送信される場合など、コンテキストに適さないことがよくあります。一方、Messageは内部例外Messageを生成しません(再帰するToStringとは異なります)。私たちが最も頻繁に必要とするのは、親と内部の例外からのすべてのメッセージである存在しないFullMessageです。
リシボブ2017年

16

拡張メソッドや再帰呼び出しは必要ありません。

try {
  // Code that throws exception
}
catch (Exception e)
{
  var messages = new List<string>();
  do
  {
    messages.Add(e.Message);
    e = e.InnerException;
  }
  while (e != null) ;
  var message = string.Join(" - ", messages);
}

鮮やかさ!考えてみたらいいのに。
ラウルマルケス

11

LINQは通常、オブジェクトのコレクションを操作するために使用されます。ただし、間違いなく、あなたの場合、オブジェクトのコレクションはありません(グラフです)。したがって、いくつかのLINQコードが可能であるとしても、私見では、それはかなり複雑または人工的です。

一方、あなたの例は、拡張メソッドが実際に合理的である主要な例のように見えます。再利用、カプセル化などの問題は言うまでもありません。

私は拡張メソッドを使用しますが、そのように実装した可能性があります。

public static string GetAllMessages(this Exception ex)
{
   if (ex == null)
     throw new ArgumentNullException("ex");

   StringBuilder sb = new StringBuilder();

   while (ex != null)
   {
      if (!string.IsNullOrEmpty(ex.Message))
      {
         if (sb.Length > 0)
           sb.Append(" ");

         sb.Append(ex.Message);
      }

      ex = ex.InnerException;
   }

   return sb.ToString();
}

しかし、それは主に味の問題です。


7

私はそうは思いません。例外はIEnumerableではないため、それ自体に対してlinqクエリを実行することはできません。

内部例外を返す拡張メソッドは次のように機能します

public static class ExceptionExtensions
{
    public static IEnumerable<Exception> InnerExceptions(this Exception exception)
    {
        Exception ex = exception;

        while (ex != null)
        {
            yield return ex;
            ex = ex.InnerException;
        }
    }
}

次に、次のようなlinqクエリを使用してすべてのメッセージを追加できます。

var allMessageText = string.Concat(exception.InnerExceptions().Select(e => e.Message + ","));

6

他の人に追加するには、メッセージを分離する方法をユーザーに決定させることができます。

    public static string GetAllMessages(this Exception ex, string separator = "\r\nInnerException: ")
    {
        if (ex.InnerException == null)
            return ex.Message;

        return ex.Message + separator + GetAllMessages(ex.InnerException, separator);
    }

6
    public static string GetExceptionMessage(Exception ex)
    {
        if (ex.InnerException == null)
        {
            return string.Concat(ex.Message, System.Environment.NewLine, ex.StackTrace);
        }
        else
        {
            // Retira a última mensagem da pilha que já foi retornada na recursividade anterior
            // (senão a última exceção - que não tem InnerException - vai cair no último else, retornando a mesma mensagem já retornada na passagem anterior)
            if (ex.InnerException.InnerException == null)
                return ex.InnerException.Message;
            else
                return string.Concat(string.Concat(ex.InnerException.Message, System.Environment.NewLine, ex.StackTrace), System.Environment.NewLine, GetExceptionMessage(ex.InnerException));
        }
    }

4

ここでは、最も簡潔なバージョンを残しておきます。

public static class ExceptionExtensions
{
    public static string GetMessageWithInner(this Exception ex) =>
        string.Join($";{ Environment.NewLine }caused by: ",
            GetInnerExceptions(ex).Select(e => $"'{ e.Message }'"));

    public static IEnumerable<Exception> GetInnerExceptions(this Exception ex)
    {
        while (ex != null)
        {
            yield return ex;
            ex = ex.InnerException;
        }
    }
}

3
public static class ExceptionExtensions
{
    public static IEnumerable<Exception> GetAllExceptions(this Exception ex)
    {
        Exception currentEx = ex;
        yield return currentEx;
        while (currentEx.InnerException != null)
        {
            currentEx = currentEx.InnerException;
            yield return currentEx;
        }
    }

    public static IEnumerable<string> GetAllExceptionAsString(this Exception ex)
    {            
        Exception currentEx = ex;
        yield return currentEx.ToString();
        while (currentEx.InnerException != null)
        {
            currentEx = currentEx.InnerException;
            yield return currentEx.ToString();
        }            
    }

    public static IEnumerable<string> GetAllExceptionMessages(this Exception ex)
    {
        Exception currentEx = ex;
        yield return currentEx.Message;
        while (currentEx.InnerException != null)
        {
            currentEx = currentEx.InnerException;
            yield return currentEx.Message;
        }
    }
}

1

ここで紹介するほとんどのソリューションには、次の実装エラーがあります。

  • null例外を処理する
  • の内部例外を処理する AggregateException
  • 再帰的な内部例外の最大深度を定義します(つまり、循環依存関係を使用)

より良い実装はここにあります:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

public static string AggregateMessages(this Exception ex) =>
    ex.GetInnerExceptions()
        .Aggregate(
            new StringBuilder(),
            (sb, e) => sb.AppendLine(e.Message),
            sb => sb.ToString());

public static IEnumerable<Exception> GetInnerExceptions(this Exception ex, int maxDepth = 5)
{
    if (ex == null || maxDepth <= 0)
    {
        yield break;
    }

    yield return ex;

    if (ex is AggregateException ax)
    {
        foreach(var i in ax.InnerExceptions.SelectMany(ie => GetInnerExceptions(ie, maxDepth - 1)))
            yield return i;
    }

    foreach (var i in GetInnerExceptions(ex.InnerException, maxDepth - 1))
        yield return i;
}

使用例:

try
{
    // ...
}
catch(Exception e)
{
    Log.Error(e, e.AggregateMessages());
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.