Alle Nachrichten von InnerException (s) erhalten?

88

Gibt es eine Möglichkeit, einen "Short Hand" -Code im LINQ-Stil zu schreiben, um zu allen Ebenen der ausgelösten InnerException (s) der Ausnahme zu gelangen? Ich würde es vorziehen, es an Ort und Stelle zu schreiben, anstatt eine Erweiterungsfunktion (wie unten) aufzurufen oder die ExceptionKlasse zu erben .

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;
    }
}; 
Jimmy
quelle
2
Darf ich Sie fragen, warum Sie etwas anderes als Erweiterungsmethoden verwenden möchten? Ihr Code sieht für mich gut aus und kann überall in Ihrem Code wiederverwendet werden.
Ken2k
@ Ken2k: Obwohl Sie die Nachrichten nicht so aufbauen möchten, wie er sie gerade hat ...
Jeff Mercado
1
@ JeffMercado Ja, aber was ist das Problem mit dem Konzept der "Erweiterungsmethode"?
Ken2k
@ ken2k: Um ehrlich zu sein, ich verstehe deine Frage nicht wirklich ... du hast gerade erwähnt, dass der Code "gut aussieht", wenn er fehlerhaft ist.
Jeff Mercado
Denken Sie nur daran, dass Sie AggregateExceptionsich kaum anders verhalten. Sie müssen InnerExceptionsstattdessen durch das Grundstück gehen. Hier finden Sie eine praktische Erweiterungsmethode: stackoverflow.com/a/52042708/661933 , um beide Fälle abzudecken.
Nawfal

Antworten:

90

Leider bietet LINQ keine Methoden an, die hierarchische Strukturen verarbeiten könnten, sondern nur Sammlungen.

Ich habe tatsächlich einige Erweiterungsmethoden, die dabei helfen könnten. Ich habe nicht den genauen Code in der Hand, aber sie sind ungefähr so:

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

In diesem Fall können Sie dann die folgenden Ausnahmen auflisten:

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

Du meinst so etwas?

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

Auf diese Weise können Sie Ihre gesamte Ausnahmehierarchie wie folgt LINQEN:

exception.GetInnerExceptions().Where(e => e.Message == "Oops!");
km
quelle
2
So viel sauberer als die vorgeschlagene Lösung
Rice
1
@Rice Bitte beachten Sie, dass die vorgeschlagene Lösung eine Verallgemeinerung dieses Problems für mehrere Abflachungsszenarien ist. Die Tatsache, dass es komplexer ist, wird erwartet.
Julealgon
31

Wie wäre es mit diesem Code:

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

Verwendung:

Console.WriteLine(e.GetExceptionMessages())

Beispiel für die Ausgabe:

Unter http: //nnn.mmm.kkk.ppp: 8000 / routingservice / router wurde kein Endpunkt abgehört , der die Nachricht akzeptieren konnte. Dies wird häufig durch eine falsche Adresse oder SOAP-Aktion verursacht. Weitere Informationen finden Sie unter InnerException, falls vorhanden.

InnerException: Es kann keine Verbindung zum Remote-Server hergestellt werden

InnerException: Es konnte keine Verbindung hergestellt werden, da der Zielcomputer diese aktiv abgelehnt hat. 127.0.0.1:8000

Vlad Gonchar
quelle
3
Sie sollten wirklich in Betracht ziehen, StringBuilderhier zu verwenden. Auch die IMO-Erweiterungsmethode sollte NullReferenceExceptionbeim Aufrufen einer Nullreferenz ausgelöst werden.
Dstarkowski
24

Ich weiß, dass dies offensichtlich ist, aber vielleicht nicht für alle.

exc.ToString();

Dies durchläuft alle Ihre inneren Ausnahmen und gibt alle Nachrichten zurück, jedoch zusammen mit der Stapelverfolgung usw.

Jiří Herník
quelle
3
Ja, das ist in Ordnung, wenn Sie glücklich sind, mit all den vollständigen Stapelspuren zu leben, die mit ToString verfälscht werden. Das passt oft nicht zum Kontext, zB wenn eine Nachricht an einen Benutzer geht. Andererseits gibt Message NICHT die innere Ausnahme Message (im Gegensatz zu ToString, das sich wiederholt). Was wir am häufigsten wollen, ist die nicht existierende FullMessage, bei der es sich ausschließlich um Nachrichten von übergeordneten und inneren Ausnahmen handelt.
Ricibob
16

Sie benötigen keine Erweiterungsmethoden oder rekursiven Aufrufe:

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);
}
4thex
quelle
Brillant! Ich wünschte, ich hätte darüber nachgedacht.
Raul Marquez
11

LINQ wird im Allgemeinen verwendet, um mit Sammlungen von Objekten zu arbeiten. In Ihrem Fall gibt es jedoch wohl keine Sammlung von Objekten (sondern eine Grafik). Obwohl ein gewisser LINQ-Code möglich sein könnte, wäre er meiner Meinung nach eher verworren oder künstlich.

Auf der anderen Seite sieht Ihr Beispiel wie ein Paradebeispiel aus, bei dem Erweiterungsmethoden tatsächlich sinnvoll sind. Ganz zu schweigen von Themen wie Wiederverwendung, Kapselung usw.

Ich würde bei einer Erweiterungsmethode bleiben, obwohl ich sie möglicherweise so implementiert hätte:

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

Aber das ist größtenteils eine Frage des Geschmacks.

Christian.K
quelle
7

Ich denke nicht, Ausnahme ist keine IEnumerable, so dass Sie keine Linq-Abfrage für eine allein durchführen können.

Eine Erweiterungsmethode zum Zurückgeben der inneren Ausnahmen würde so funktionieren

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

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

Sie können dann alle Nachrichten mit einer Linq-Abfrage wie folgt anhängen:

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

Zum Hinzufügen zu anderen möchten Sie den Benutzer möglicherweise entscheiden lassen, wie die Nachrichten getrennt werden sollen:

    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);
    }
Zottelig
quelle
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));
        }
    }
Ronaldo Rodrigues Lagoeiro Mar.
quelle
4

Ich werde hier nur die prägnanteste Version belassen:

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;
        }
    }
}
Dmitry Karpenko
quelle
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;
        }
    }
}
Kishore Kumar
quelle
1

Die meisten hier vorgestellten Lösungen weisen die folgenden Implementierungsfehler auf:

  • Griff null Ausnahmen
  • Behandle die inneren Ausnahmen von AggregateException
  • Definieren Sie eine maximale Tiefe für wiederkehrende innere Ausnahmen (dh mit kreisförmigen Abhängigkeiten).

Eine bessere Implementierung ist dies hier:

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

Anwendungsbeispiel:

try
{
    // ...
}
catch(Exception e)
{
    Log.Error(e, e.AggregateMessages());
}
MovGP0
quelle