¿Recibes todos los mensajes de InnerException (s)?

92

¿Hay alguna forma de escribir un código de "mano corta" estilo LINQ para caminar a todos los niveles de InnerException (s) de Exception lanzadas? Preferiría escribirlo en su lugar en lugar de llamar a una función de extensión (como se muestra a continuación) o heredar la Exceptionclase.

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;
    }
}; 
Palanqueta
fuente
2
¿Puedo preguntarle por qué quiere usar algo más que métodos de extensión? Su código se ve bien para mí y es reutilizable en todas partes en su código.
ken2k
@ ken2k: Aunque no querría construir los mensajes de la forma en que lo tiene ahora ...
Jeff Mercado
1
@JeffMercado Sí, pero ¿cuál es el problema con el concepto de "método de extensiones"?
ken2k
@ ken2k: Para ser honesto, realmente no entiendo tu pregunta ... acabas de mencionar que el código "se ve bien" cuando tiene fallas.
Jeff Mercado
1
Solo tenga en cuenta que AggregateExceptionlas mentes se comportan un poco diferente. En su lugar, tendrá que atravesar la InnerExceptionspropiedad. Proporcionó un método de extensión útil aquí: stackoverflow.com/a/52042708/661933 para cubrir ambos casos.
nawfal

Respuestas:

92

Desafortunadamente, LINQ no ofrece métodos que puedan procesar estructuras jerárquicas, solo colecciones.

De hecho, tengo algunos métodos de extensión que podrían ayudar a hacer esto. No tengo el código exacto en la mano, pero son algo como esto:

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

Entonces, en este caso, puede hacer esto para enumerar las excepciones:

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
fuente
81

¿Te refieres a algo como esto?

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

De esta manera, podría LINQ en toda su jerarquía de excepciones, así:

exception.GetInnerExceptions().Where(e => e.Message == "Oops!");
km
fuente
2
Mucho más limpio que la solución propuesta
Rice
1
@Rice tenga en cuenta que la solución propuesta es una generalización de este problema para múltiples escenarios de aplanamiento. Se espera que sea más complejo.
julealgon
31

¿Qué tal este código?

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

Uso:

Console.WriteLine(e.GetExceptionMessages())

Ejemplo de salida:

No había ningún punto final escuchando en http: //nnn.mmm.kkk.ppp: 8000 / routingservice / router que pudiera aceptar el mensaje. A menudo, esto se debe a una dirección incorrecta o una acción SOAP. Consulte InnerException, si está presente, para obtener más detalles.

InnerException: no se puede conectar al servidor remoto

InnerException: No se pudo establecer la conexión porque la máquina de destino la rechazó activamente 127.0.0.1:8000

Vlad Gonchar
fuente
3
Realmente deberías considerar usar StringBuilderaquí. También el método de extensión de la OMI debe lanzarse NullReferenceExceptioncuando se invoca en una referencia nula.
dstarkowski
27

Sé que esto es obvio, pero tal vez no para todos.

exc.ToString();

Esto revisará todas sus excepciones internas y devolverá todos los mensajes, pero junto con el seguimiento de la pila, etc.

Jiří Herník
fuente
3
Sí, eso está bien si estás feliz de vivir con todo el rastro de pila completo que se destruye con ToString. Eso a menudo no se adapta al contexto, por ejemplo, si el mensaje va a un usuario. Por otro lado, Message NO da el mensaje de excepción interno (a diferencia de ToString, que se repite). Lo que queremos con mayor frecuencia es el FullMessage no existente, que es todo mensaje de excepciones internas y principales.
Ricibob
16

No necesita métodos de extensión ni llamadas recursivas:

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
fuente
¡Brillante! Ojalá lo hubiera pensado.
Raul Marquez
11

LINQ se usa generalmente para trabajar con colecciones de objetos. Sin embargo, podría decirse que en su caso no hay una colección de objetos (sino un gráfico). Entonces, aunque podría ser posible algún código LINQ, en mi humilde opinión sería bastante complicado o artificial.

Por otro lado, su ejemplo parece un excelente ejemplo en el que los métodos de extensión son realmente razonables. Por no hablar de cuestiones como la reutilización, la encapsulación, etc.

Me quedaría con un método de extensión, aunque podría haberlo implementado de esa manera:

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

Pero eso es en gran parte una cuestión de gusto.

Christian.K
fuente
7

No lo creo, la excepción no es un IEnumerable, por lo que no puede realizar una consulta de linq contra una por sí sola.

Un método de extensión para devolver las excepciones internas funcionaría así

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

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

luego podría agregar todos los mensajes usando una consulta linq como esta:

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

Para agregar a otros, es posible que desee dejar que el usuario decida cómo separar los mensajes:

    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);
    }
Lanudo
fuente
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
fuente
4

Solo voy a dejar la versión más concisa aquí:

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
fuente
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
fuente
1

La mayoría de las soluciones presentadas aquí tienen los siguientes errores de implementación:

  • manejar nullexcepciones
  • manejar las excepciones internas de AggregateException
  • definir una profundidad máxima para excepciones internas recurrentes (es decir, con dependencias circulares)

Una mejor implementación es esta aquí:

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

Uso de ejemplo:

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