107

Is there any way to write a LINQ style "short hand" code for walking to all levels of InnerException(s) of Exception thrown? I would prefer to write it in place instead of calling an extension function (as below) or inheriting the Exception class.

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;
    }
}; 
Jimmy
  • 3,224
  • 5
  • 29
  • 47
  • 2
    May I ask you why you want to use something else than Extension methods? Your code looks fine to me, and is reusable everywhere in your code. – ken2k Feb 16 '12 at 15:45
  • @ken2k: Though you wouldn't want to build up the messages the way he has it right now... – Jeff Mercado Feb 16 '12 at 16:11
  • 1
    @JeffMercado Yes, but what's the problem with the concept of "extensions method"? – ken2k Feb 16 '12 at 16:12
  • @ken2k: To be honest, I don't really understand your question... you just mentioned that the code "looks fine" when it is flawed. – Jeff Mercado Feb 16 '12 at 16:14
  • 1
    Just beware in mind `AggregateException`s behave little different. You will have to walk through `InnerExceptions` property instead. Provided a handy extension method here: https://stackoverflow.com/a/52042708/661933 to cover both cases. – nawfal Aug 27 '18 at 15:48

16 Answers16

104

Unfortunately LINQ doesn't offer methods that could process hierarchical structures, only collections.

I actually have some extension methods that could help do this. I don't have the exact code in hand but they're something like this:

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

Then in this case you could do this to enumerate through the exceptions:

public static string GetaAllMessages(this Exception exception)
{
    var messages = exception.FromHierarchy(ex => ex.InnerException)
        .Select(ex => ex.Message);
    return String.Join(Environment.NewLine, messages);
}
MatthewMartin
  • 32,326
  • 33
  • 105
  • 164
Jeff Mercado
  • 129,526
  • 32
  • 251
  • 272
100

You mean something like this?

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

This way you could LINQ over your entire exceptions hierarchy, like this:

exception.GetInnerExceptions().Where(e => e.Message == "Oops!");
Kiquenet
  • 14,494
  • 35
  • 148
  • 243
k.m
  • 30,794
  • 10
  • 62
  • 86
  • 2
    So much cleaner than the proposed solution – Rice Aug 22 '18 at 23:31
  • 2
    @Rice please note that the proposed solution is a generalization of this problem for multiple flattening scenarios. The fact that it is more complex is expected. – julealgon Feb 12 '20 at 20:26
37

For those, who are waiting for a one-liner.

exc.ToString();

This will go through all your inner exceptions and return all messages, the downside is that it will also contain stack traces, etc.

Jiří Herník
  • 2,412
  • 1
  • 25
  • 26
  • 5
    Yeh thats fine if your happy to live with all the full stack trace that get blatted with ToString. That often doesn't suit the context e.g if message is going to a user. On the other hand Message does NOT give the inner exception Message (unlike ToString which does recurse). What we most often want is the non existant FullMessage which is all message from parent and inner exceptions. – Ricibob Oct 05 '17 at 13:19
35

How about this code:

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

Usage:

Console.WriteLine(e.GetExceptionMessages())

Example of output:

There was no endpoint listening at http://nnn.mmm.kkk.ppp:8000/routingservice/router that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details.

InnerException: Unable to connect to the remote server

InnerException: No connection could be made because the target machine actively refused it 127.0.0.1:8000

Orace
  • 7,822
  • 30
  • 45
Vlad Gonchar
  • 495
  • 4
  • 5
  • 3
    You should really consider using `StringBuilder` here. Also IMO extension method should throw `NullReferenceException` when invoked on null reference. – dstarkowski Nov 23 '16 at 15:47
27

You don't need extension methods or recursive calls:

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);
}
4thex
  • 1,094
  • 1
  • 9
  • 21
11

LINQ is generally used to work with collections of objects. However, arguably, in your case there is no collection of objects (but a graph). So even though some LINQ code might be possible, IMHO it would be rather convoluted or artificial.

On the other hand, your example looks like a prime example where extension methods are actually reasonable. Not to speak of issues like reuse, encapsulation, etc.

I would stay with an extension method, although I might have implemented it that way:

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

But that is largely an issue of taste.

Christian.K
  • 47,778
  • 10
  • 99
  • 143
8

I don't think so, exception is not an IEnumerable so you can't perform a linq query against one on its own.

An extension method to return the inner exceptions would work like this

public static class ExceptionExtensions
{
    public static IEnumerable<Exception> InnerExceptions(this Exception exception)
    {
        Exception ex = exception;

        while (ex != null)
        {
            yield return ex;
            ex = ex.InnerException;
        }
    }
}

you could then append all the messages using a linq query like this:

var allMessageText = string.Concat(exception.InnerExceptions().Select(e => e.Message + ","));
Trevor Pilley
  • 16,156
  • 5
  • 44
  • 60
6

To add to others, you may want to let the user decide on how to separate the messages:

    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);
    }
Shaggy
  • 61
  • 1
  • 3
6
    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));
        }
    }
6

Most solutions presended here have the following implementation errors:

  • handle null exceptions
  • handle the inner exceptions of AggregateException
  • define a max depth for recurse inner exceptions (ie. with circular dependencies)

A better implementation is this here:

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

Example usage:

try
{
    // ...
}
catch(Exception e)
{
    Log.Error(e, e.AggregateMessages());
}
MovGP0
  • 7,267
  • 3
  • 49
  • 42
4

I'm just going to leave the most concise version here:

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;
        }
    }
}
Dmitry Karpenko
  • 544
  • 5
  • 6
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;
        }
    }
}
Kishore Kumar
  • 12,675
  • 27
  • 97
  • 154
1

Just use the following code.

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

    return currentEx;
}
1

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

enter image description here

Neomaster
  • 166
  • 1
  • 7
0

After 9+ years the original question still begs for an answer.

Not exactly short, but yes, it can be done LINQ-style in a single statement:

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
*/

The key is to use Enumerable.Range().Aggregate() for iteration and a value v of anonymous type (introduced in C# 3.0) holding both

  • the result v.s being built up, as well as
  • the current exception v.ex as we're walking down the list.

(StringBuilder left out to reduce clutter.)

salvis
  • 11
  • 2
0

I used combination of Select and Join:

Unit test:

  [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 class:

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

Final message will be:

"One or more errors occurred. Possible reasons: MY_ERROR_MESSAGE_1, MY_ERROR_MESSAGE_2, MY_ERROR_MESSAGE_3"
Mr.B
  • 3,484
  • 2
  • 26
  • 40