在不使用 while 循环的情况下找到最内层的异常?

当 C # 抛出异常时,它可能有一个内部异常。我想要做的是得到最内部的异常,或者换句话说,叶子异常,没有一个内部的异常。我可以在 while 循环中做到这一点:

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

但我想知道是否有一些俏皮话,我可以用来代替这一点。

48031 次浏览

Oneliner :)

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

Obviously, you can't make it any simpler.

As said in this answer by Glenn McElhoe, it's the only reliable way.

You could use recursion to create a method in a utility class somewhere.

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

Use:

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

The extension method suggested (good idea @dtb)

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

Use:

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

If you don't know how deep the inner exceptions are nested, there is no way around a loop or recursion.

Of course, you can define an extension method that abstracts this away:

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

Sometimes you might have many inner exceptions (many bubbled exceptions). In which case you might want to do:

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

Not quite one line but close:

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

I believe Exception.GetBaseException() does the same thing as these solutions.

Caveat: From various comments we've figured out it doesn't always literally do the same thing, and in some cases the recursive/iterating solution will get you further. It is usually the innermost exception, which is disappointingly inconsistent, thanks to certain types of Exceptions that override the default. However if you catch specific types of exceptions and make reasonably sure they're not oddballs (like AggregateException) then I would expect it gets the legitimate innermost/earliest exception.

In fact is so simple, you could use Exception.GetBaseException()

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

I know this is an old post, but I'm surprised nobody suggested GetBaseException() which is a method on the Exception class:

catch (Exception x)
{
var baseException = x.GetBaseException();
}

This has been around since .NET 1.1. Documentation here: http://msdn.microsoft.com/en-us/library/system.exception.getbaseexception(v=vs.71).aspx

Looping through InnerExceptions is the only reliable way.

If the caught exception is an AggregateException, then GetBaseException() returns only the innermost AggregateException.

http://msdn.microsoft.com/en-us/library/system.aggregateexception.getbaseexception.aspx

You have to loop, and having to loop, it's cleaner to move the loop into a separate function.

I created an extension method to deal with this. It returns a list of all of the inner exceptions of the specified type, chasing down Exception.InnerException and AggregateException.InnerExceptions.

In my particular problem, chasing down the inner exceptions was more complicated than usual, because the exceptions were being thrown by the constructors of classes that were being invoked through reflection. The exception we were catching had an InnerException of type TargetInvocationException, and the exceptions we actually needed to look at were buried deep in the tree.

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

Usage is pretty simple. If, for example, you want to know if we encountered a

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

Another way you could do it is by calling GetBaseException() twice:

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

This works because if it is an AggregateException, the first call gets you to the innermost non-AggregateException then the second call gets you to the innermost exception of that exception. If the first exception is not an AggregateException, then the second call just returns the same exception.

I ran into this and wanted to be able to list all of the exception messages from the exception "stack". So, I came up with this.

public static string GetExceptionMessages(Exception ex)
{
if (ex.InnerException is null)
return ex.Message;
else return $"{ex.Message}\n{GetExceptionMessages(ex.InnerException)}";
}