4
\$\begingroup\$

How do you handle AggregateException? It is really useful in many TPL or business scenarios, but handling is a real pain. I defined the following three extensions to help myself a little bit. They can be used this way:

try
{
  throw new AggregateException(
     new ArgumentException(), 
     new DivideByZeroException());
}
catch (AggregateException aex)
{   
  aex
     .Handle((ArgumentException ex) =>
     {
         Console.WriteLine(ex.Message);
     })
     .Handle((DivideByZeroException ex) =>
     {
         Console.WriteLine(ex.Message);
         throw ex;
     })
     .Throw<DivideByZeroException>()
     .Throw<ArgumentException>()
     .ThrowIfNonEmpty();
}

These methods are defined on AggregateException and return AggregateException so they can be chained.

Handle invokes parameter delegate once per each inner exception instance of a specified type and returns a new copy of AggregateException without those handled exceptions. An optional exception being thrown in the delegate will be added to the resulting AggregateException.

Throw method tries to find a first inner exception instance of specified type and throws it if found. You can chain them to decide on priority.

ThrowIfNonEmpty extends an AggregateException to throw it when InnerExceptions collection is not empty.

public static class AggregateExceptionExtensions
{
    public static AggregateException Handle<TEx>(
        this AggregateException source, Action<TEx> handler)
        where TEx : Exception
    {
        var exceptions = source.Flatten().InnerExceptions.OfType<TEx>();
        var handlerExceptions = new List<Exception>();
        foreach (var exception in exceptions)
            try
            {
                handler(exception);
            }
            catch (Exception ex)
            {
                handlerExceptions.Add(ex);
            }

        return new AggregateException(source.InnerExceptions
            .Except(exceptions)
            .Concat(handlerExceptions));
    }

    public static AggregateException Throw<TEx>(this AggregateException source)
    {
        var ex = source.InnerExceptions.FirstOrDefault(e => e is TEx);
        if (ex != null)
            throw ex;

        return source;
    }

    public static void ThrowIfNonEmpty(this AggregateException source)
    {
        if (source.InnerExceptions.Any())
            throw source;
    }
}

Can you suggest any other tricks which might be useful?

\$\endgroup\$
2
\$\begingroup\$

I don't think it's a good idea to rethrow only the first/single exception of a particular type. Often, when done right ;-) each exception may contain different data about what went wrong, like entity validation errors may happen for multiple entities or mutliple files may not have been found etc. If you pick only the first one you won't know about the rest and you'll app crashes agian next time.

The Handle and the ThrowIfNonEmpty are fine but Throw is very harmful.

Additionaly I would change the Handle to be Func<TEx, bool>:

public static AggregateException Handle<TEx>(
    this AggregateException source, Func<TEx, bool> handler)
    where TEx : Exception
{
    var exceptions = source.Flatten().InnerExceptions.OfType<TEx>();
    var handlerExceptions = new List<Exception>();
    foreach (var exception in exceptions)
    {
        try
        {
            if (!handler(exception)) 
            {
                handlerExceptions.Add(exception);
            }
        }
        catch (Exception ex)
        {
            handlerExceptions.Add(ex);
        }
    }
    return new AggregateException(source.InnerExceptions
        .Except(exceptions)
        .Concat(handlerExceptions));
}

so that I can for example log it and still be able to say I didn't handle it by returning false.

.Handle((ArgumentException ex) =>
{
   Console.WriteLine(ex.Message);
   return false;
})
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.