Отримуєте всі повідомлення від InnerException (s)?


92

Чи є спосіб написати код "короткої руки" у стилі LINQ для прогулянки на всі рівні викидів InnerException (s)? Я вважав за краще писати його на місці, а не викликати функцію розширення (як показано нижче) або успадковувати Exceptionклас.

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
Чи можу я запитати вас, чому ви хочете використовувати щось інше, ніж методи розширення? Мені здається, що ваш код чудовий і його можна повторно використовувати скрізь.
ken2k

@ ken2k: Хоча ви не хотіли б формувати повідомлення так, як він це має зараз ...
Джефф Меркадо

1
@JeffMercado Так, але в чому проблема концепції "методу розширень"?
ken2k

@ ken2k: Чесно кажучи, я насправді не розумію вашого запитання ... Ви щойно згадали, що код "виглядає нормально", коли він невірний.
Джефф Меркадо,

1
Тільки пам’ятайте, пам’ятайте, AggregateExceptionщо вони поводяться трохи інакше. InnerExceptionsНатомість вам доведеться пройтись по власності. Тут наведено зручний метод розширення: stackoverflow.com/a/52042708/661933 для охоплення обох випадків.
nawfal

Відповіді:


92

На жаль, LINQ не пропонує методів, які можуть обробляти ієрархічні структури, лише колекції.

Я насправді маю кілька методів розширення, які можуть допомогти у цьому. У мене немає точного коду, але вони приблизно такі:

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

Тоді в цьому випадку ви можете зробити це для перерахування винятків:

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

Ви маєте на увазі щось подібне?

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

Таким чином, ви можете LINQ по всій своїй ієрархії винятків, наприклад:

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

2
Набагато чистіше, ніж запропоноване рішення
Райс,

1
@Rice, будь ласка, зверніть увагу, що запропоноване рішення є узагальненням цієї проблеми для декількох сценаріїв вирівнювання. Очікується той факт, що він є більш складним.
julealgon

31

Як щодо цього коду:

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

Використання:

Console.WriteLine(e.GetExceptionMessages())

Приклад результату:

На http: //nnn.mmm.kkk.ppp: 8000 / routingservice / router не було прослуховування кінцевої точки, яка могла б прийняти повідомлення. Це часто спричинено неправильною адресою або дією SOAP. Докладніше див. У розділі InnerException, якщо він присутній.

InnerException: Не вдається підключитися до віддаленого сервера

InnerException: Не вдалося встановити з'єднання, оскільки цільова машина активно відмовляла йому 127.0.0.1:8000


3
Ви дійсно повинні розглянути можливість використання StringBuilderтут. Також метод розширення IMO повинен кидати, NullReferenceExceptionколи його викликають за нульовим посиланням.
dstarkowski

27

Я знаю, що це очевидно, але, можливо, не для всіх.

exc.ToString();

Це пройде всі ваші внутрішні винятки і поверне всі повідомлення, але разом із трасуванням стека тощо.


3
Так, це прекрасно, якщо ви щасливі жити з усіма повними слідами стека, які отримують з допомогою ToString. Це часто не відповідає контексту, наприклад, якщо повідомлення надходить користувачеві. З іншого боку, повідомлення НЕ дає внутрішнього повідомлення про виключення (на відміну від ToString, яке повторюється). Найчастіше ми хочемо неіснуючого FullMessage, який є всім повідомленням батьківських та внутрішніх винятків.
Ricibob

16

Вам не потрібні методи розширення або рекурсивні виклики:

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

Блискуче! Як би я не думав про це.
Рауль Маркес,

11

LINQ зазвичай використовується для роботи з колекціями об'єктів. Однак, безперечно, у вашому випадку немає колекції об’єктів (але є графік). Отже, навіть незважаючи на те, що певний код LINQ може бути можливим, IMHO він буде скоріше заплутаним чи штучним.

З іншого боку, ваш приклад виглядає як прекрасний приклад, коли методи розширення насправді є розумними. Не кажучи вже про такі питання, як повторне використання, інкапсуляція тощо.

Я б дотримувався методу розширення, хоча, можливо, реалізував би це так:

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

Але це багато в чому питання смаку.


7

Я не думаю, що виняток - це не IEnumerable, тому ви не можете самостійно виконати запит linq щодо такого.

Метод розширення для повернення внутрішніх винятків буде працювати так

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

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

потім ви можете додати всі повідомлення, використовуючи запит linq, такий:

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

6

Щоб додати до інших, ви можете дозволити користувачеві вирішити, як розділити повідомлення:

    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

Я просто залишу тут найкоротшу версію:

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

Більшість запропонованих тут рішень мають такі помилки реалізації:

  • обробляти nullвинятки
  • обробляти внутрішні винятки AggregateException
  • визначити максимальну глибину для рекурсивних внутрішніх винятків (тобто з круговими залежностями)

Тут краща реалізація:

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

Приклад використання:

try
{
    // ...
}
catch(Exception e)
{
    Log.Error(e, e.AggregateMessages());
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.