从InnerException获取所有消息?


92

有什么方法可以编写LINQ样式的“简写”代码,以遍历所有级别的抛出InnerException的异常?我宁愿就地编写它,而不是调用扩展函数(如下所示)或继承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 2012年

@ ken2k:尽管您现在不想以他现在的方式来建立消息……
Jeff Mercado 2012年

1
@JeffMercado是的,但是“扩展方法”的概念有什么问题?
ken2k 2012年

@ ken2k:说实话,我真的不明白您的问题……您刚刚提到代码有缺陷时“看起来不错”。
Jeff Mercado 2012年

1
只是要谨记,AggregateException行为上几乎没有什么不同。您将不得不遍历InnerExceptions物业。在此处提供了一种方便的扩展方法:stackoverflow.com/a/52042708/661933,涵盖了两种情况。
nawfal

Answers:


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
比拟议的解决方案更加清洁
Rice

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在这里使用。NullReferenceException在对null引用调用时,IMO扩展方法也应抛出。
dstarkowski

27

我知道这很明显,但可能并非全部。

exc.ToString();

这将遍历所有内部异常并返回所有消息,但连同堆栈跟踪等。


3
是的,如果您愿意接受与ToString绑定的所有完整堆栈跟踪,那就很好。这通常不适合上下文,例如,如果消息要发送给用户。另一方面,Message不会给出内部异常消息(与确实递归的ToString不同)。我们最经常需要的是不存在的FullMessage,它是来自父级和内部异常的所有消息。
里奇博布

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代码,恕我直言,它还是相当复杂或人为的。

另一方面,您的示例看起来像是扩展方法实际上合理的主要示例。更不用说重用,封装等问题了。

尽管我可能以这种方式实现了扩展方法,但我将继续使用它:

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());
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.