Получаете все сообщения от InnerException (ов)?


92

Есть ли способ написать «сокращенный» код в стиле LINQ для перехода ко всем уровням создаваемых InnerException (ов) Exception? Я бы предпочел написать его на месте вместо вызова функции расширения (как показано ниже) или наследования 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 05

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.