有没有办法编写一个LINQ样式的“简写”代码,用于遍历抛出Exception的所有级别的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;
}
};
答案 0 :(得分:71)
不幸的是,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);
}
答案 1 :(得分:60)
你的意思是这样的吗?
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 :(得分:27)
这段代码怎么样:
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 :(得分:17)
我知道这很明显,但也许不是全部。
exc.ToString();
这将遍历所有内部异常并返回所有消息,但与堆栈跟踪等一起。
答案 4 :(得分:10)
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();
}
但这主要是品味问题。
答案 5 :(得分:7)
您不需要扩展方法或递归调用:
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);
}
答案 6 :(得分: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);
}
答案 7 :(得分:5)
我不这么认为,异常不是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 + ","));
答案 8 :(得分:4)
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 :(得分: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;
}
}
}
答案 10 :(得分:3)
我将在这里留下最简洁的版本:
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 :(得分:2)
此处预设的大多数解决方案都有以下实现错误:
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());
}
答案 12 :(得分:0)
只需使用以下代码。
catch(Exception ex)
{
Exception currentEx = ex;
while (currentEx.InnerException != null)
{
currentEx = currentEx.InnerException;
}
return currentEx;
}
答案 13 :(得分:0)
在 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 以减少混乱。)