Ricevi tutti i messaggi da InnerException?


92

Esiste un modo per scrivere un codice "short hand" in stile LINQ per raggiungere tutti i livelli di InnerException di eccezione generata? Preferirei scriverlo sul posto invece di chiamare una funzione di estensione (come sotto) o ereditare la Exceptionclasse.

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
Posso chiederti perché vuoi usare qualcos'altro oltre ai metodi di estensione? Il tuo codice mi sembra a posto ed è riutilizzabile ovunque nel tuo codice.
ken2k

@ ken2k: Anche se non vorresti costruire i messaggi nel modo in cui li ha in questo momento ...
Jeff Mercado

1
@ JeffMercado Sì, ma qual è il problema con il concetto di "metodo delle estensioni"?
ken2k

@ ken2k: Ad essere sincero, non capisco davvero la tua domanda ... hai appena detto che il codice "sembra a posto" quando è difettoso.
Jeff Mercado,

1
Fai solo attenzione perché si AggregateExceptioncomportano in modo leggermente diverso. Dovrai InnerExceptionsinvece attraversare la proprietà. Fornito un pratico metodo di estensione qui: stackoverflow.com/a/52042708/661933 per coprire entrambi i casi.
nawfal

Risposte:


92

Sfortunatamente LINQ non offre metodi in grado di elaborare strutture gerarchiche, solo raccolte.

In realtà ho alcuni metodi di estensione che potrebbero aiutare a farlo. Non ho il codice esatto in mano ma sono qualcosa del genere:

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

Quindi in questo caso potresti farlo per enumerare le eccezioni:

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

intendi qualcosa del genere?

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

In questo modo potresti usare LINQ su tutta la tua gerarchia di eccezioni, in questo modo:

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

2
Molto più pulito della soluzione proposta
Rice

1
@Rice si prega di notare che la soluzione proposta è una generalizzazione di questo problema per più scenari di appiattimento. Ci si aspetta che sia più complesso.
julealgon

31

Che ne dici di questo codice:

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

Utilizzo:

Console.WriteLine(e.GetExceptionMessages())

Esempio di output:

Nessun endpoint in ascolto su http: //nnn.mmm.kkk.ppp: 8000 / routingservice / router in grado di accettare il messaggio. Ciò è spesso causato da un indirizzo errato o da un'azione SOAP. Vedi InnerException, se presente, per maggiori dettagli.

InnerException: impossibile connettersi al server remoto

InnerException: non è stato possibile stabilire alcuna connessione perché la macchina di destinazione l'ha rifiutata attivamente 127.0.0.1:8000


3
Dovresti davvero considerare di usarlo StringBuilderqui. Anche il metodo di estensione IMO dovrebbe generare NullReferenceExceptionquando invocato su riferimento null.
dstarkowski

27

So che è ovvio, ma forse non per tutti.

exc.ToString();

Questo passerà attraverso tutte le tue eccezioni interne e restituirà tutti i messaggi, ma insieme allo stack trace ecc.


3
Sì, va bene se sei felice di vivere con tutta la traccia dello stack completa che viene blattata con ToString. Questo spesso non si adatta al contesto, ad esempio se il messaggio sta per un utente. D'altra parte Message NON fornisce l'eccezione interna Message (a differenza di ToString che ricorre). Quello che più spesso desideriamo è il FullMessage inesistente che è tutto il messaggio dal genitore e dalle eccezioni interne.
Ricibob

16

Non hai bisogno di metodi di estensione o chiamate ricorsive:

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

Brillante! Vorrei averci pensato.
Raul Marquez

11

LINQ viene generalmente utilizzato per lavorare con raccolte di oggetti. Tuttavia, probabilmente, nel tuo caso non esiste una raccolta di oggetti (ma un grafico). Quindi, anche se un po 'di codice LINQ potrebbe essere possibile, IMHO sarebbe piuttosto contorto o artificiale.

D'altra parte, il tuo esempio sembra un ottimo esempio in cui i metodi di estensione sono effettivamente ragionevoli. Per non parlare di questioni come il riutilizzo, l'incapsulamento, ecc.

Rimanerei con un metodo di estensione, anche se avrei potuto implementarlo in questo modo:

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

Ma è soprattutto una questione di gusto.


7

Non credo, l'eccezione non è un IEnumerable, quindi non è possibile eseguire una query linq su uno da solo.

Un metodo di estensione per restituire le eccezioni interne funzionerebbe in questo modo

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

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

puoi quindi aggiungere tutti i messaggi usando una query linq come questa:

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

6

Per aggiungere altri, potresti voler consentire all'utente di decidere come separare i messaggi:

    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

Lascio qui solo la versione più concisa:

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

La maggior parte delle soluzioni qui presentate presenta i seguenti errori di implementazione:

  • gestire le nulleccezioni
  • gestire le eccezioni interne di AggregateException
  • definire una profondità massima per le eccezioni interne di ricorsione (ad es. con dipendenze circolari)

Un'implementazione migliore è questa qui:

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

Utilizzo di esempio:

try
{
    // ...
}
catch(Exception e)
{
    Log.Error(e, e.AggregateMessages());
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.