Is it possible to handle exceptions within LINQ queries?

Viewed 28141

Example:

myEnumerable.Select(a => ThisMethodMayThrowExceptions(a));

How to make it work even if it throws exceptions? Like a try catch block with a default value case an exceptions is thrown...

10 Answers
/// <summary>
/// Catch the exception and then omit the value if exception thrown.
/// </summary>
public static IEnumerable<T> Catch<T>(this IEnumerable<T> source, Action<Exception> action = null)
{
    return Catch<T, Exception>(source, action);
}


/// <summary>
/// Catch the exception and then omit the value if exception thrown.
/// </summary>
public static IEnumerable<T> Catch<T, TException>(this IEnumerable<T> source, Action<TException> action = null) where TException : Exception
{
    using var enumerator = source.GetEnumerator();
    while(true)
    {
        T item;
        try
        {
            if (!enumerator.MoveNext())
                break;
            item = enumerator.Current;
        }
        catch (TException e)
        {
            action?.Invoke(e);
            continue;
        }
        yield return item;
    }
}

/// <summary>
/// Catch the exception and then return the default value.
/// </summary>
public static IEnumerable<T> Catch<T>(this IEnumerable<T> source, Func<Exception, T> defaultValue)
{
    return Catch<T, Exception>(source, defaultValue);
}

/// <summary>
/// Catch the exception and then return the default value.
/// </summary>
public static IEnumerable<T> Catch<T, TException>(this IEnumerable<T> source, Func<TException, T> defaultValue) where TException : Exception
{
    using var enumerator = source.GetEnumerator();
    while(true)
    {
        T item;
        try
        {
            if (!enumerator.MoveNext())
                break;
            item = enumerator.Current;
        }
        catch (TException e)
        {
            item = defaultValue(e);
        }
        yield return item;
    }
}

Usage:

myEnumerable.Select(a => ThisMethodMayThrowExceptions(a)).Catch(e => Console.WriteLine(e.Message));

myEnumerable.Select(a => ThisMethodMayThrowExceptions(a)).Catch(e => default);

myEnumerable.Select(a => ThisMethodMayThrowExceptions(a)).Catch();

myEnumerable.Select(a => ThisMethodMayThrowExceptions(a)).Catch(((InvalidOperationException) e) => Console.WriteLine(e.Message));

myEnumerable.Select(a => ThisMethodMayThrowExceptions(a)).Catch(((InvalidOperationException) e) => default);

I've created small library for this purposes. It's supported exception handling for Select, SelectMany and Where operators. Usage example:

var target = source.AsCatchable() // move source to catchable context
    .Select(v => int.Parse(v)) // can throw an exception
    .Catch((Exception e) => { /* some action */ }, () => -1) 
    .Select(v => v * 2)
    .ToArray();

which equivalet to

var target = source
    .Select(v => 
        {
            try
            {
                return int.Parse(v);
            }
            catch (Exception)
            {
                return -1; // some default behaviour 
            }
        })
    .Select(v => v * 2)
    .ToArray();

It's also possible to handle several types of exceptions

var collection = Enumerable.Range(0, 5)
                .AsCatchable()
                .Select(v =>
                {
                    if (v == 2) throw new ArgumentException("2");
                    if (v == 3) throw new InvalidOperationException("3");
                    return v.ToString();
                })
                .Catch((ArgumentException e) => { /*  */ }, v => "ArgumentException")
                .Catch((InvalidOperationException e) => { /*  */ }, v => "InvalidOperationException")
                .Catch((Exception e) => { /*  */ })
                .ToList();

wrap ThisMethodMayThrowExceptions with a new function,

myEnumerable.Select(a =>         ThisMethodMayThrowExceptions(a)); //old
myEnumerable.Select(a => tryCall(ThisMethodMayThrowExceptions,a)); //new

it's a generic function, with try catch inside.

T2 tryCall<T1, T2>(Func<T1, T2> fn, T1 input, T2 exceptionValue = default)
{
    try
    {
        return fn(input);
    }
    catch
    {
        return exceptionValue;
    }
}

var numbers = new [] {"1", "a"};
numbers.Select(n => tryCall(double.Parse, n));             //1, 0
numbers.Select(n => tryCall(double.Parse, n, double.NaN)); //1, NaN

I believe this is the correct answer since it allows you to handle the troublesome item and it is filtered from the end result.


public static class IEnumerableExtensions {

    public static IEnumerable<TResult> SelectWithExceptionHandler<T, TResult>(this IEnumerable<T> enumerable, Func<T, TResult> func, Action<T, Exception> handler)
        => enumerable
            .Select(x => {
                try {
                    return (true, func(x));
                } catch (Exception error) {
                    handler(x, error);
                }
                return default;
            })
            .Where(x => x.Item1)
            .Select(x => x.Item2);

}

Related