Is it possible to handle exceptions within LINQ queries?

26,317

Solution 1

myEnumerable.Select(a => 
  {
    try
    {
      return ThisMethodMayThrowExceptions(a));
    }
    catch(Exception)
    {
      return defaultValue;
    }
  });

But actually, it has some smell.

About the lambda syntax:

x => x.something

is kind of a shortcut and could be written as

(x) => { return x.something; }

Solution 2

Call a projection which has that try/catch:

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

...

public static Bar TryThisMethod(Foo a)
{
     try
     {
         return ThisMethodMayThrowExceptions(a);
     }
     catch(BarNotFoundException)
     {
         return Bar.Default;
     }
}

Admittedly I'd rarely want to use this technique. It feels like an abuse of exceptions in general, but sometimes there are APIs which leave you no choice.

(I'd almost certainly put it in a separate method rather than putting it "inline" as a lambda expression though.)

Solution 3

In case you need Expression instead of lambda function (e.g. when selecting from IQueryable), you can use something like this:

public static class ExpressionHelper
{
    public static Expression<Func<TSource, TResult>> TryDefaultExpression<TSource, TResult>(Expression<Func<TSource, TResult>> success, TResult defaultValue)
    {
        var body = Expression.TryCatch(success.Body, Expression.Catch(Expression.Parameter(typeof(Exception)), Expression.Constant(defaultValue, typeof (TResult))));
        var lambda = Expression.Lambda<Func<TSource, TResult>>(body, success.Parameters);

        return lambda;
    }
}

Usage:

[Test]
public void Test()
{
    var strings = new object [] {"1", "2", "woot", "3", Guid.NewGuid()}.AsQueryable();
    var ints = strings.Select(ExpressionHelper.TryDefaultExpression<object, int>(x => Convert.ToInt32(x), 0));
    Assert.IsTrue(ints.SequenceEqual(new[] {1, 2, 0, 3, 0}));
}

Solution 4

I have come with a small extension when I quickly want to try/catch every iteration of an IEnumerable<T>

Usage

public void Test()
{
    List<string> completedProcesses = initialEnumerable
        .SelectTry(x => RiskyOperation(x))
        .OnCaughtException(exception => { _logger.Error(exception); return null; })
        .Where(x => x != null) // filter the ones which failed
        .ToList();
}

The extension

public static class OnCaughtExceptionExtension
{
    public static IEnumerable<SelectTryResult<TSource, TResult>> SelectTry<TSource, TResult>(this IEnumerable<TSource> enumerable, Func<TSource, TResult> selector)
    {
        foreach (TSource element in enumerable)
        {
            SelectTryResult<TSource, TResult> returnedValue;
            try
            {
                returnedValue = new SelectTryResult<TSource, TResult>(element, selector(element), null);
            }
            catch (Exception ex)
            {
                returnedValue = new SelectTryResult<TSource, TResult>(element, default(TResult), ex);
            }
            yield return returnedValue;
        }
    }

    public static IEnumerable<TResult> OnCaughtException<TSource, TResult>(this IEnumerable<SelectTryResult<TSource, TResult>> enumerable, Func<Exception, TResult> exceptionHandler)
    {
        return enumerable.Select(x => x.CaughtException == null ? x.Result : exceptionHandler(x.CaughtException));
    }

    public static IEnumerable<TResult> OnCaughtException<TSource, TResult>(this IEnumerable<SelectTryResult<TSource, TResult>> enumerable, Func<TSource, Exception, TResult> exceptionHandler)
    {
        return enumerable.Select(x => x.CaughtException == null ? x.Result : exceptionHandler(x.Source, x.CaughtException));
    }

    public class SelectTryResult<TSource,TResult>
    {
        internal SelectTryResult(TSource source, TResult result, Exception exception)
        {
            Source = source;
            Result = result;
            CaughtException = exception;
        }

        public TSource Source { get; private set; }
        public TResult Result { get; private set; }
        public Exception CaughtException { get; private set; }
    }
}

We could eventually go a bit further by having a SkipOnException extension, accepting optionally an exception handler for example.

Solution 5

A variation of Stefan's solution for comprehension syntax:

from a in myEnumerable
select (new Func<myType>(() => {
    try
    {
        return ThisMethodMayThrowExceptions(a));
    }
    catch(Exception)
    {
        return defaultValue;
    }
}))();

Although, it "smells" too, but still this approach can sometimes be used for running code with side-effects inside expression.

Share:
26,317
Jader Dias
Author by

Jader Dias

Perl, Javascript, C#, Go, Matlab and Python Developer

Updated on October 29, 2021

Comments

  • Jader Dias
    Jader Dias over 2 years

    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...

  • Pluc
    Pluc over 8 years
    What do you consider an abuse of exception? In my business logic, I have a method to calculate an employee's pay. There are various things that can go wrong like the employee not having a salary set. I made a custom exception I can throw and catch in my controller and put in my view model. That's bad?
  • Jon Skeet
    Jon Skeet over 8 years
    @Pluc: Is the employee meant to have a salary set? If so, it being missing sounds exceptional. If it's somewhat expected, I probably wouldn't use exceptions to handle it.
  • Theodor Zoulias
    Theodor Zoulias over 3 years
    FYI the library System.Interactive includes also a Catch operator. You should probably check the overloads of that operator, to ensure that there will be no ambiguity in case someone wants to use your library and that library combined.
  • Artem Prokudin
    Artem Prokudin over 3 years
    Thanks for your clarification! AsCatchable returns object of ICatchableEnumerable<T>. Overrided Select, SelectMany, Where (and Catch) also work with this interface. So it works without ambiguity. I've created new interface because yield-like implementation for IEnumerable (which is used in System.Interactive) leads to iteration stopping after first exception (MoveNext() always returns false after throwing).
  • Gert Arnold
    Gert Arnold about 3 years
    Yet another version of Select + a try/catch method block. All answers here do that. But worse, this method doesn't do anything.
  • Gert Arnold
    Gert Arnold about 3 years
    Exceptions in ThisMethodMayThrowExceptions(a) aren't caught.
  • shtse8
    shtse8 about 3 years
    @GertArnold it will be caught exceptions in ThisMethodMayThrowExceptions(a) because this was run in enumerator.Current
  • Gert Arnold
    Gert Arnold about 3 years
    Not in a test I did. Add a minimal reproducible example to prove your point.
  • Gert Arnold
    Gert Arnold about 3 years
    Yet, they ask for a default value to be returned. Your method doesn't do that and, also, it introduces a side effect in a LINQ statement. I'm still convinced that this isn't the right approach. Any known exceptions should be dealt with in the method passed to the LINQ statement, or exceptions from the LINQ statement as a whole should be caught.
  • shtse8
    shtse8 about 3 years
    @GertArnold for the question, what I get is an example "like" returning a default value, but not a must requirement, while some people may want to catch the exception. So, for the Catch extension, we can modify it to have two overloads: Action for catching and Func<T> for returning default value.
  • shtse8
    shtse8 about 3 years
    @GertArnold for right approach, some people like Linq, while some people like foreach and then catch, or even some people like Reactive. There is no absolute right approach, it depends. For me, if the project use Linq most, please use all Linq. if the project like for, then please do all in for. if most are Reactive, please use Reactive for all.
  • Theodor Zoulias
    Theodor Zoulias about 3 years
    I would prefer to access the enumerator.Current inside the try block. It is highly unlikely to throw, but who knows how a custom enumerator could behave?
  • Theodor Zoulias
    Theodor Zoulias about 3 years
    You could also consider adding a ///<summary></summary> on top of the Catch operator, to document its behavior. It's not totally obvious from its name that the operator omits yielding the failed elements.
  • shtse8
    shtse8 about 3 years
    @TheodorZoulias Added and provided more overload for different scenarios.