Obtenir tous les messages d'InnerException (s)?


92

Existe-t-il un moyen d'écrire un code "short hand" de style LINQ pour accéder à tous les niveaux d'InnerException (s) of Exception lancée? Je préférerais l'écrire sur place au lieu d'appeler une fonction d'extension (comme ci-dessous) ou d'hériter de 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
Puis-je vous demander pourquoi vous souhaitez utiliser autre chose que les méthodes d'extension? Votre code me semble correct et est réutilisable partout dans votre code.
ken2k

@ ken2k: Bien que vous ne voudriez pas construire les messages comme il les a en ce moment ...
Jeff Mercado

1
@JeffMercado Oui, mais quel est le problème avec le concept de "méthode d'extensions"?
ken2k

@ ken2k: Pour être honnête, je ne comprends pas vraiment votre question ... vous venez de mentionner que le code "a l'air bien" quand il est défectueux.
Jeff Mercado

1
Méfiez-vous simplement des AggregateExceptioncomportements un peu différents. Vous devrez InnerExceptionsplutôt traverser la propriété. Fourni une méthode d'extension pratique ici: stackoverflow.com/a/52042708/661933 pour couvrir les deux cas.
nawfal

Réponses:


92

Malheureusement, LINQ n'offre pas de méthodes qui pourraient traiter des structures hiérarchiques, uniquement des collections.

J'ai en fait des méthodes d'extension qui pourraient aider à le faire. Je n'ai pas le code exact en main mais c'est quelque chose comme ça:

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

Ensuite, dans ce cas, vous pouvez le faire pour énumérer les exceptions:

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

Tu veux dire quelque chose comme ca?

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 cette façon, vous pouvez LINQ sur toute votre hiérarchie d'exceptions, comme ceci:

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

2
Tellement plus propre que la solution proposée
Rice

1
@Rice veuillez noter que la solution proposée est une généralisation de ce problème pour plusieurs scénarios d'aplatissement. Le fait qu'il soit plus complexe est attendu.
julealgon le

31

Que diriez-vous de ce 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;
}

Usage:

Console.WriteLine(e.GetExceptionMessages())

Exemple de sortie:

Il n'y avait aucun point de terminaison à l'écoute sur http: //nnn.mmm.kkk.ppp: 8000 / routingservice / router qui pourrait accepter le message. Cela est souvent dû à une adresse incorrecte ou à une action SOAP. Voir InnerException, le cas échéant, pour plus de détails.

InnerException: impossible de se connecter au serveur distant

InnerException: aucune connexion n'a pu être établie car la machine cible l'a activement refusée 127.0.0.1:8000


3
Vous devriez vraiment envisager d'utiliser StringBuilderici. La méthode d'extension IMO doit également être lancée NullReferenceExceptionlorsqu'elle est appelée sur une référence nulle.
dstarkowski

27

Je sais que c'est évident, mais peut-être pas pour tout.

exc.ToString();

Cela passera par toutes vos exceptions internes et retournera tous les messages, mais avec la trace de la pile, etc.


3
Ouais c'est bien si vous êtes heureux de vivre avec toute la trace de pile complète qui est blattée avec ToString. Cela ne convient souvent pas au contexte, par exemple si le message est destiné à un utilisateur. D'autre part, Message ne donne PAS l'exception interne Message (contrairement à ToString qui récurent). Ce que nous voulons le plus souvent, c'est le FullMessage inexistant qui est tout le message des exceptions parent et interne.
Ricibob

16

Vous n'avez pas besoin de méthodes d'extension ou d'appels récursifs:

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

Brillant! J'aurais aimé y penser.
Raul Marquez

11

LINQ est généralement utilisé pour travailler avec des collections d'objets. Cependant, sans doute, dans votre cas, il n'y a pas de collection d'objets (mais un graphique). Donc, même si un code LINQ pourrait être possible, à mon humble avis, il serait plutôt alambiqué ou artificiel.

D'un autre côté, votre exemple ressemble à un excellent exemple où les méthodes d'extension sont réellement raisonnables. Sans parler de problèmes tels que la réutilisation, l'encapsulation, etc.

Je resterais avec une méthode d'extension, même si je l'aurais peut-être implémentée de cette façon:

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

Mais c'est en grande partie une question de goût.


7

Je ne pense pas, l'exception n'est pas un IEnumerable, vous ne pouvez donc pas effectuer une requête linq contre un seul.

Une méthode d'extension pour renvoyer les exceptions internes fonctionnerait comme ceci

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

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

vous pouvez ensuite ajouter tous les messages en utilisant une requête linq comme celle-ci:

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

6

Pour en ajouter d'autres, vous souhaiterez peut-être laisser l'utilisateur décider comment séparer les messages:

    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

Je vais juste laisser la version la plus concise ici:

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 plupart des solutions proposées ici comportent les erreurs d'implémentation suivantes:

  • gérer les nullexceptions
  • gérer les exceptions internes de AggregateException
  • définir une profondeur maximale pour les exceptions internes récurrentes (c'est-à-dire avec des dépendances circulaires)

Une meilleure implémentation est la suivante:

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

Exemple d'utilisation:

try
{
    // ...
}
catch(Exception e)
{
    Log.Error(e, e.AggregateMessages());
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.