Exceptions in .NET can be difficult to log or inspect due to their hierarchical nature with inner exceptions. Since the InnerException on a given exception is not an enumerated list, it requires a little bit of work to flatten the hierarchy.
Using generic extensions, it pretty easy to flatten any given object which has the structure of a .NET exception (Parent -> Child1 -> Child2 -> Child3) where each child is mapped to a single instance property on the parent. An expression passed to the extension will define which property is the child.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public static class Extensions { // Flatten any object of type TSource that has a single child of type TSource public static IEnumerable<TSource> Flatten<TSource>( this TSource source, Expression<Func<TSource, object >> expression) { var tobj = source; var memberExpression = (MemberExpression)expression.Body; var propertyInfo = (PropertyInfo)memberExpression.Member; do { yield return tobj; tobj = (TSource)propertyInfo.GetValue(tobj); } while (tobj != null ); } } |
Then, to use the extension, it might look like the below code. I’m defining a nested Exception and then using the extension and a LINQ aggregate to get a single string containing all exception messages.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | void Main() { // Create a nested exception var exception = new Exception( "message" , new Exception( "inner message1" , new Exception( "inner message2" , new Exception( "" , // No message new Exception( "inner message4" ) ) ) ) ); var message = exception .Flatten<Exception>(x => x.InnerException) .Where(x => ! string .IsNullOrWhiteSpace(x.Message)) .Select(x => x.Message) .Aggregate((current, next) => $ "{current} | {next}" ); Console.WriteLine(message); } |
The nice thing about the extension method is that it is can be used for, essentially, any type rather than being limited to Exceptions specifically.