Obtendo todas as mensagens de InnerException (s)?


92

Existe alguma maneira de escrever um código de "mão curta" do estilo LINQ para percorrer todos os níveis de InnerException (ões) de Exception lançada? Eu preferiria escrever no lugar em vez de chamar uma função de extensão (como abaixo) ou herdar a 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 perguntar por que você deseja usar algo diferente dos métodos de extensão? Seu código parece bom para mim e pode ser reutilizado em qualquer lugar do seu código.
ken2k

@ ken2k: Embora você não queira construir as mensagens do jeito que ele está fazendo agora ...
Jeff Mercado

1
@JeffMercado Sim, mas qual é o problema com o conceito de "método de extensões"?
ken2k

@ ken2k: Para ser honesto, eu realmente não entendi sua pergunta ... você acabou de mencionar que o código "parece bom" quando está falho.
Jeff Mercado de

1
Apenas tome cuidado com AggregateExceptiono comportamento um pouco diferente. Em InnerExceptionsvez disso, você terá que percorrer a propriedade. Fornecido um método de extensão útil aqui: stackoverflow.com/a/52042708/661933 para cobrir ambos os casos.
nawfal

Respostas:


94

Infelizmente o LINQ não oferece métodos que possam processar estruturas hierárquicas, apenas coleções.

Na verdade, tenho alguns métodos de extensão que podem ajudar a fazer isso. Não tenho o código exato em mãos, mas eles são mais ou menos assim:

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

Então, neste caso, você poderia fazer isso para enumerar por meio das exceções:

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

83

Você quer dizer algo assim?

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

Dessa forma, você pode usar o LINQ em toda a hierarquia de exceções, assim:

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

2
Muito mais limpo do que a solução proposta
Rice

1
@Rice, observe que a solução proposta é uma generalização desse problema para vários cenários de nivelamento. O fato de ser mais complexo é esperado.
julealgon

33

Que 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())

Exemplo de saída:

Não havia endpoint escutando em http: //nnn.mmm.kkk.ppp: 8000 / routingservice / roteador que pudesse aceitar a mensagem. Isso geralmente é causado por um endereço incorreto ou ação SOAP. Consulte InnerException, se presente, para obter mais detalhes.

InnerException: Incapaz de conectar ao servidor remoto

InnerException: Nenhuma conexão pôde ser feita porque a máquina de destino a recusou ativamente 127.0.0.1:8000


3
Você realmente deve considerar usar StringBuilderaqui. Além disso, o método de extensão IMO deve lançar NullReferenceExceptionquando chamado em referência nula.
dstarkowski

27

Para aqueles que estão esperando por uma linha.

exc.ToString();

Isso percorrerá todas as suas exceções internas e retornará todas as mensagens; a desvantagem é que também conterá rastreamentos de pilha, etc.


3
Sim, está tudo bem se você está feliz em viver com todo o rastreamento de pilha completo que é manchado com ToString. Isso geralmente não se ajusta ao contexto, por exemplo, se a mensagem está indo para um usuário. Por outro lado, Message NÃO fornece a exceção interna Message (ao contrário de ToString, que recursiva). O que mais frequentemente queremos é a FullMessage inexistente, que é toda mensagem do pai e exceções internas.
Ricibob

16

Você não precisa de métodos de extensão ou chamadas 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);
}

Brilhante! Gostaria de ter pensado nisso.
Raul Marquez

11

O LINQ é geralmente usado para trabalhar com coleções de objetos. No entanto, sem dúvida, no seu caso não há coleção de objetos (mas um gráfico). Portanto, embora algum código LINQ seja possível, IMHO ele seria bastante complicado ou artificial.

Por outro lado, seu exemplo parece um exemplo excelente onde os métodos de extensão são realmente razoáveis. Sem falar em questões como reutilização, encapsulamento, etc.

Eu ficaria com um método de extensão, embora pudesse tê-lo implementado dessa forma:

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

Mas isso é em grande parte uma questão de gosto.


7

Acho que não, a exceção não é um IEnumerable, portanto, você não pode executar uma consulta linq em um sozinho.

Um método de extensão para retornar as exceções internas funcionaria assim

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

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

você poderia então anexar todas as mensagens usando uma consulta linq como esta:

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

6

Para adicionar a outras pessoas, você pode permitir que o usuário decida como separar as mensagens:

    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

Vou apenas deixar a versão mais concisa aqui:

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

2

A maioria das soluções apresentadas aqui têm os seguintes erros de implementação:

  • lidar com nullexceções
  • lidar com as exceções internas de AggregateException
  • definir uma profundidade máxima para exceções internas recursivas (ou seja, com dependências circulares)

Uma implementação melhor é esta aqui:

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

Exemplo de uso:

try
{
    // ...
}
catch(Exception e)
{
    Log.Error(e, e.AggregateMessages());
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.