找到最内层的异常而不使用while循环?


82

当C#引发异常时,它可以具有内部异常。我想要做的是获取最内部的异常,换句话说,没有内部异常的叶子异常。我可以在while循环中这样做:

while (e.InnerException != null)
{
    e = e.InnerException;
}

但是我想知道是否可以使用一线纸来代替。


4
我在一个类中引发了异常,但是我的类正被一个吞噬所有异常并引发自身异常的库使用。问题是,库异常非常通用,我需要知道我抛出了哪个特定的异常才能知道如何处理该问题。更糟的是,该库将多次相互嵌套嵌套抛出自己的异常,达到任意深度。因此,例如,它将抛出LibraryException -> LibraryException -> LibraryException -> MyException。我的异常始终是链中的最后一个,并且没有自己的内部异常。
Daniel T.

哦,我明白了为什么您自己这样做(以及变体,例如从特定类型派生的最里面的东西)后,可能会跳到最内层,但我不知道问题是什么。
乔恩·汉娜

哦,我明白你的意思。我只是想知道是否还有另一种编写方式。我已经习惯于LINQ做几乎所有的事情,以至于当我不得不编写一个循环时,这似乎很奇怪。我主要处理数据访问,因此ICollections几乎可以完成所有工作。
Daniel T.

@Daniel,LINQ不仅掩盖了代码仍然必须循环的事实吗?.NET中是否有任何真正的基于集合的命令?
Brad 2010年

6
最正确的答案是潜伏在底部(目前)只有2票-Exception.GetBaseException()。这基本上一直存在于框架中。感谢batCattle进行完整性检查。
杰伊(Jay)

Answers:



121

我相信Exception.GetBaseException()这些解决方案的作用相同。

警告:从各种评论中我们发现,它并不总是从字面上做同样的事情,在某些情况下,递归/迭代解决方案将使您走得更远。它通常是最内部的异常,这令人失望的不一致,这是由于某些类型的Exceptions覆盖了默认值。但是,如果您捕获特定类型的异常并合理地确定它们不是奇怪的异常(如AggregateException),那么我希望它会得到合法的最内部/最早的异常。


3
+1没有什么比知道BCL来避免复杂的解决方案了。显然,这是最正确的答案。
杰伊

4
由于某种原因,GetBaseException()没有返回第一个根异常。
塔里克

4
Glenn McElhoe指出,实际上,GetBaseException()并不总是按照MSDN的建议进行一般的预期(“异常是一个或多个后续异常的根本原因”)。在AggregateException中,它仅限于同一类型的最低异常,也许还有其他例外。
2014年

1
对我的问题不起作用。我的水平比提供的水平低了许多。
Giovanni B

2
GetBaseException()不适用于我,因为最顶层的异常是AggregateException。
克里斯·巴伦斯

21

遍历InnerExceptions是唯一可靠的方法。

如果捕获的异常是AggregateException,则GetBaseException()仅返回最里面的AggregateException。

http://msdn.microsoft.com/zh-CN/library/system.aggregateexception.getbaseexception.aspx


1
接得好。谢谢-解释了上面的评论,该回答对某些人不起作用。这清楚表明,MSDN对“一个或多个后续异常的根本原因”的一般描述并不总是您所假定的,因为派生类可能会覆盖它。真正的规则似乎是,一连串的异常都在GetBaseException()上“同意”并返回相同的对象,AggregateException似乎就是这种情况。
2014年

12

如果您不知道内部异常嵌套的深度,则无法绕过循环或递归。

当然,您可以定义一个扩展方法来对此进行抽象:

public static class ExceptionExtensions
{
    public static Exception GetInnermostException(this Exception e)
    {
        if (e == null)
        {
            throw new ArgumentNullException("e");
        }

        while (e.InnerException != null)
        {
            e = e.InnerException;
        }

        return e;
    }
}


2

有时您可能有许多内部异常(许多冒泡异常)。在这种情况下,您可能需要执行以下操作:

List<Exception> es = new List<Exception>();
while(e.InnerException != null)
{
   es.add(e.InnerException);
   e = e.InnerException
}

1

一行不多,但很接近:

        Func<Exception, Exception> last = null;
        last = e => e.InnerException == null ? e : last(e.InnerException);

1

其实很简单,你可以使用 Exception.GetBaseException()

Try
      //Your code
Catch ex As Exception
      MessageBox.Show(ex.GetBaseException().Message, My.Settings.MsgBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error);
End Try

感谢您的解决方案!很少有人想到VB:D
Federico Navarrete

1
这是有原因的!:P
尤达

1

您必须循环,而且必须循环,将循环移到一个单独的函数中比较干净。

我创建了一个扩展方法来处理此问题。它返回指定类型的所有内部异常的列表,跟踪Exception.InnerException和AggregateException.InnerExceptions。

在我的特定问题中,追逐内部异常比平常更为复杂,因为异常是由通过反射调用的类的构造函数抛出的。我们捕获的异常具有类型为TargetInvocationException的InnerException,而我们实际需要查看的异常被深埋在树中。

public static class ExceptionExtensions
{
    public static IEnumerable<T> innerExceptions<T>(this Exception ex)
        where T : Exception
    {
        var rVal = new List<T>();

        Action<Exception> lambda = null;
        lambda = (x) =>
        {
            var xt = x as T;
            if (xt != null)
                rVal.Add(xt);

            if (x.InnerException != null)
                lambda(x.InnerException);

            var ax = x as AggregateException;
            if (ax != null)
            {
                foreach (var aix in ax.InnerExceptions)
                    lambda(aix);
            }
        };

        lambda(ex);

        return rVal;
    }
}

用法很简单。例如,如果您想知道我们是否遇到过

catch (Exception ex)
{
    var myExes = ex.innerExceptions<MyException>();
    if (myExes.Any(x => x.Message.StartsWith("Encountered my specific error")))
    {
        // ...
    }
}

什么事Exception.GetBaseException()
Kiquenet '18 -4-9

1

您可以使用递归在某个实用程序类中的某个地方创建方法。

public Exception GetFirstException(Exception ex)
{
    if(ex.InnerException == null) { return ex; } // end case
    else { return GetFirstException(ex.InnerException); } // recurse
}

使用:

try
{
    // some code here
}
catch (Exception ex)
{
    Exception baseException = GetFirstException(ex);
}

建议的扩展方法(@dtb好主意)

public static Exception GetFirstException(this Exception ex)
{
    if(ex.InnerException == null) { return ex; } // end case
    else { return GetFirstException(ex.InnerException); } // recurse
}

使用:

try
{
    // some code here
}
catch (Exception ex)
{
    Exception baseException = ex.GetFirstException();
}

有用 public static Exception GetOriginalException(this Exception ex) { if (ex.InnerException == null) return ex; return ex.InnerException.GetOriginalException(); }
Kiquenet 2015年

不适用AggregateException.InnerExceptions
Kiquenet '18 -4-9

0

您可以执行的另一种方法是调用GetBaseException()两次:

Exception innermostException = e.GetBaseException().GetBaseException();

之所以有效,是因为如果它是一个AggregateException,则第一个调用将您带到最内部的非AggregateException异常,然后第二个调用将您带到该异常的最内部异常。如果第一个异常不是AggregateException,则第二个调用仅返回相同的异常。


0

我遇到了这个问题,希望能够列出来自异常“堆栈”的所有异常消息。所以,我想到了这个。

public static string GetExceptionMessages(Exception ex)
{
    if (ex.InnerException is null)
        return ex.Message;
    else return $"{ex.Message}\n{GetExceptionMessages(ex.InnerException)}";
}
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.