【问题标题】:Getting all messages from InnerException(s)?从 InnerException(s) 获取所有消息?
【发布时间】:2012-03-08 00:31:25
【问题描述】:

有没有办法编写一个 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;
    }
}; 

【问题讨论】:

  • 请问您为什么要使用扩展方法以外的东西?您的代码对我来说看起来不错,并且可以在您的代码中的任何地方重用。
  • @ken2k:虽然你不想像他现在那样建立消息......
  • @JeffMercado 是的,但是“扩展方法”的概念有什么问题?
  • @ken2k:说实话,我不太明白你的问题……你刚刚提到代码有缺陷时“看起来很好”。
  • 请注意AggregateExceptions 的行为略有不同。您将不得不走过InnerExceptions 属性。这里提供了一个方便的扩展方法:stackoverflow.com/a/52042708/661933 涵盖这两种情况。

标签: c# c#-4.0


【解决方案1】:

你的意思是这样的?

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!");

【讨论】:

  • 比建议的解决方案干净得多
  • @Rice 请注意,建议的解决方案是针对多个扁平化场景的此问题的概括。预计它会更复杂。
【解决方案2】:

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

但这主要是品味问题。

【讨论】:

    【解决方案3】:

    我不这么认为,异常不是 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 + ","));
    

    【讨论】:

      【解决方案4】:

      很遗憾,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);
      }
      

      【讨论】:

      • 一个漂亮的解决方案,但是如果出现错误AggregateException就不起作用...
      • 那么在这种情况下,您需要考虑将这些异常展开为有意义的事情。
      【解决方案5】:
      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;
              }
          }
      }
      

      【讨论】:

        【解决方案6】:

        这段代码怎么样:

        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

        【讨论】:

        • 你真的应该考虑在这里使用StringBuilder。当在空引用上调用时,IMO 扩展方法也应该抛出 NullReferenceException
        【解决方案7】:

        要添加到其他人,您可能希望让用户决定如何分隔消息:

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

        【讨论】:

          【解决方案8】:
              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));
                  }
              }
          

          【讨论】:

            【解决方案9】:

            对于那些正在等待单线的人。

            exc.ToString();
            

            这将遍历你所有的内部异常并返回所有消息,缺点是它还会包含堆栈跟踪等。

            【讨论】:

            • 是的,如果您乐于接受所有被 ToString 吹捧的完整堆栈跟踪,那很好。这通常不适合上下文,例如,如果消息要发送给用户。另一方面,Message 不会给出内部异常 Message(与 ToString 不同,它确实递归)。我们最常想要的是不存在的 FullMessage,它是来自父异常和内部异常的所有消息。
            【解决方案10】:

            我只想在这里留下最简洁的版本:

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

            【讨论】:

              【解决方案11】:

              您不需要扩展方法或递归调用:

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

              【讨论】:

              • 太棒了!希望我能考虑一下。
              • 这是一种比其他响应更简洁的方法。
              【解决方案12】:

              这里提出的大多数解决方案都有以下实现错误:

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

              【讨论】:

                【解决方案13】:

                只需使用以下代码。

                catch(Exception ex)
                {
                    Exception currentEx = ex;
                    while (currentEx.InnerException != null)
                    {
                        currentEx = currentEx.InnerException;
                    }
                
                    return currentEx;
                }
                

                【讨论】:

                  【解决方案14】:

                  9 年多之后,最初的问题仍然需要答案。

                  不是很短,但是是的,可以在单个语句中完成 LINQ 样式:

                  var ex1 = new NullReferenceException("EX1");
                  var ex2 = new InvalidCastException("EX2", ex1);
                  var ex3 = new InvalidOperationException("EX3", ex2);
                  const int maxDepth = 10;
                  
                  var message = Enumerable.Range(1, maxDepth).Aggregate(
                    new { s = $"{ex3.GetType().Name} - {ex3.Message}", ex = ex3.InnerException },
                    (v, i) => v.ex != null
                              ? new { s = v.s + $"\nInner exception {i}: {v.ex.GetType().Name} - {v.ex.Message}",
                                      ex = v.ex.InnerException }
                              : new { s = v.s, ex = (Exception)null },
                    v => v.s);
                  
                  /* message is:
                  InvalidOperationException - EX3
                  Inner exception 1: InvalidCastException - EX2
                  Inner exception 2: NullReferenceException - EX1
                  */
                  

                  关键是使用 Enumerable.Range().Aggregate() 进行迭代,并使用匿名类型的值 v(在 C# 3.0 中引入)同时保持两者

                  • v.s 正在构建的结果,以及
                  • 当前的异常 v.ex 我们正在查看列表。

                  (省略了 StringBuilder 以减少混乱。)

                  【讨论】:

                    【解决方案15】:

                    我使用了 Select 和 Join 的组合:

                    单元测试:

                      [Test]
                        public void FirendlyErrorMessage_Tests()
                        {
                            // Arrange
                            Exception ex = new AggregateException(new Exception("MY_ERROR_MESSAGE_FROM_DCE_1"), new Exception("MY_ERROR_MESSAGE_FROM_DCE_2"), new Exception("MY_ERROR_MESSAGE_FROM_DCE_3"));
                    
                          
                            // Assert
                            var e = Assert.Throws<Exception>(() => ErrorHandler.RaiseFirendlyErrorMessage(ex));
                    
                            Assert.AreEqual(e.Message, "One or more errors occurred. Possible reasons: MY_ERROR_MESSAGE_1, MY_ERROR_MESSAGE_2, MY_ERROR_MESSAGE_3");
                        }
                    

                    ErrorHandler 类:

                        public void RaiseFirendlyErrorMessage(Exception ex)
                        {
                            if (ex is AggregateException)
                            {
                                var aggrEx = ex as AggregateException;
                                string aggregateExcMessage = ex.Message + $" Possible reasons: { string.Join(", ", aggrEx.InnerExceptions.Select(s => s.Message)) }";
                                throw new Exception(aggregateExcMessage);
                            }
                        }
                    

                    最后的信息是:

                    "One or more errors occurred. Possible reasons: MY_ERROR_MESSAGE_1, MY_ERROR_MESSAGE_2, MY_ERROR_MESSAGE_3"
                    

                    【讨论】:

                      【解决方案16】:

                      出现 AggregateException

                      public static List<Exception> GetInnerExceptions(this Exception e)
                      {
                          List<Exception> eList = new List<Exception>();
                                  
                          if (e is AggregateException)
                          {
                              eList.AddRange((e as AggregateException).InnerExceptions);
                          }
                          else
                          {
                              eList.Add(e);
                          }
                      
                          List<Exception> ieList = eList
                              .Where(i => i.InnerException != null)
                              .SelectMany(i => i.InnerException.GetInnerExceptions())
                              .ToList();
                      
                          eList.AddRange(ieList);
                      
                          return eList;
                      }
                      

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 2012-02-10
                        • 1970-01-01
                        • 2021-03-30
                        • 1970-01-01
                        • 2020-10-02
                        • 1970-01-01
                        • 2014-09-02
                        相关资源
                        最近更新 更多