Pobierasz wszystkie wiadomości od InnerException (s)?


92

Czy istnieje sposób na napisanie kodu „krótkiej ręki” w stylu LINQ umożliwiającego przejście do wszystkich zgłoszonych wyjątków InnerException (s)? Wolałbym napisać to w miejscu zamiast wywoływać funkcję rozszerzającą (jak poniżej) lub dziedziczyć Exceptionklasę.

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
Czy mogę zapytać, dlaczego chcesz użyć czegoś innego niż metody rozszerzające? Twój kod wygląda dla mnie dobrze i można go ponownie wykorzystać w całym kodzie.
ken2k

@ ken2k: Chociaż nie chciałbyś budować wiadomości tak, jak ma to teraz ...
Jeff Mercado,

1
@JeffMercado Tak, ale jaki jest problem z pojęciem „metody rozszerzeń”?
ken2k

@ ken2k: Szczerze mówiąc, nie rozumiem twojego pytania… wspomniałeś właśnie, że kod "wygląda dobrze", gdy jest wadliwy.
Jeff Mercado,

1
Tylko uważaj, AggregateExceptionzachowuj się trochę inaczej. Zamiast tego będziesz musiał przejść przez InnerExceptionsposiadłość. Podano tutaj przydatną metodę rozszerzenia: stackoverflow.com/a/52042708/661933, aby objąć oba przypadki.
nawfal

Odpowiedzi:


92

Niestety LINQ nie oferuje metod, które mogłyby przetwarzać struktury hierarchiczne, tylko kolekcje.

Właściwie mam kilka metod rozszerzających, które mogą w tym pomóc. Nie mam dokładnego kodu, ale jest to coś takiego:

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

Wtedy w tym przypadku możesz to zrobić, aby wyliczyć przez wyjątki:

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

Masz na myśli coś takiego?

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

W ten sposób możesz LINQ w całej hierarchii wyjątków, na przykład:

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

2
O wiele czystszy niż proponowane rozwiązanie
Rice

1
@Rice proszę zauważyć, że proponowane rozwiązanie jest uogólnieniem tego problemu dla wielu scenariuszy spłaszczania. Oczekuje się, że jest to bardziej złożone.
julealgon

31

A co z tym kodem:

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

Stosowanie:

Console.WriteLine(e.GetExceptionMessages())

Przykład wyniku:

Nie było punktu końcowego nasłuchującego pod adresem http: //nnn.mmm.kkk.ppp: 8000 / routingservice / router, który mógłby zaakceptować wiadomość. Jest to często spowodowane nieprawidłowym adresem lub działaniem protokołu SOAP. Aby uzyskać więcej informacji, zobacz InnerException, jeśli występuje.

InnerException: nie można połączyć się z serwerem zdalnym

InnerException: nie można nawiązać połączenia, ponieważ maszyna docelowa aktywnie odmówiła 127.0.0.1:8000


3
Naprawdę powinieneś rozważyć użycie StringBuildertutaj. Również metoda rozszerzenia IMO powinna zostać wyrzucona, NullReferenceExceptiongdy zostanie wywołana w odwołaniu o wartości null.
dstarkowski

27

Wiem, że to oczywiste, ale może nie dla wszystkich.

exc.ToString();

Spowoduje to przejście przez wszystkie wewnętrzne wyjątki i zwrócenie wszystkich wiadomości, ale razem ze śledzeniem stosu itp.


3
Tak, to w porządku, jeśli cieszysz się, że żyjesz z pełnym śladem stosu, który jest wysadzany za pomocą ToString. To często nie pasuje do kontekstu, np. Gdy wiadomość jest kierowana do użytkownika. Z drugiej strony Message NIE daje wewnętrznego komunikatu wyjątku (w przeciwieństwie do ToString, który wykonuje recurse). To, czego najczęściej chcemy, to nieistniejący FullMessage, który jest wiadomością od rodziców i wyjątków wewnętrznych.
Ricibob

16

Nie potrzebujesz metod rozszerzających ani wywołań rekurencyjnych:

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

Znakomity! Szkoda, że ​​nie pomyślałem o tym.
Raul Marquez

11

LINQ jest zwykle używany do pracy z kolekcjami obiektów. Jednak prawdopodobnie w twoim przypadku nie ma kolekcji obiektów (ale wykres). Więc nawet jeśli jakiś kod LINQ może być możliwy, IMHO byłby raczej zawiły lub sztuczny.

Z drugiej strony, Twój przykład wygląda jak główny przykład, w którym metody rozszerzające są w rzeczywistości rozsądne. Nie wspominając o problemach, takich jak ponowne użycie, hermetyzacja itp.

Pozostałbym przy metodzie rozszerzenia, chociaż mógłbym to zaimplementować w ten sposób:

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

Ale to w dużej mierze kwestia gustu.


7

Nie sądzę, wyjątek nie jest IEnumerable, więc nie można samodzielnie wykonać zapytania linq przeciwko jednemu.

Metoda rozszerzenia zwracająca wewnętrzne wyjątki działałaby w ten sposób

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

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

możesz następnie dołączyć wszystkie wiadomości za pomocą zapytania linq w następujący sposób:

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

6

Aby dodać do innych, możesz pozwolić użytkownikowi zdecydować, jak rozdzielić wiadomości:

    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

Tutaj zostawię najbardziej zwięzłą wersję:

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

Większość prezentowanych tutaj rozwiązań ma następujące błędy implementacyjne:

  • obsługi nullwyjątków
  • obsłużyć wewnętrzne wyjątki AggregateException
  • zdefiniować maksymalną głębokość dla wewnętrznych wyjątków cyklicznych (tj. z zależnościami cyklicznymi)

Lepsza implementacja jest taka:

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

Przykładowe użycie:

try
{
    // ...
}
catch(Exception e)
{
    Log.Error(e, e.AggregateMessages());
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.