42

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

Jader Dias
  • 88,211
  • 155
  • 421
  • 625
  • It depends very much on the return type of the method if this should even be done. In most cases this would not be a healthy code pattern, because in general, true to the paradigm, set operations should succeed atomically. – Gert Arnold Mar 13 '21 at 09:58
  • @GertArnold Can't agree. Catching known exception is always better than nothing. – shtse8 Mar 15 '21 at 09:16
  • @shtse8 Yes, but not *inside* a LINQ statement, most of the times. – Gert Arnold Mar 15 '21 at 09:18
  • @GertArnold so that why we need to implement a `Catch` operator to make our life easier. – shtse8 Mar 15 '21 at 09:19
  • 1
    @shtse8 take a look at the [`Catch`](https://github.com/dotnet/reactive/blob/main/Ix.NET/Source/System.Interactive/System/Linq/Operators/Catch.cs) operator, from the [System.Interactive](https://www.nuget.org/packages/System.Interactive/) package. The `MoveNext` call must be placed inside the `try` block. – Theodor Zoulias Mar 15 '21 at 10:00
  • @TheodorZoulias you are right. So I have deleted it and left this question. btw, hope some people can post a better answer here for others to not returning a default value or filtering null value or even install packages to solve. – shtse8 Mar 15 '21 at 10:02
  • @shtse8 there is nothing wrong with installing a package IMHO, especially when it's owned by the [dotnetfoundation](https://www.nuget.org/profiles/dotnetfoundation). But you could also consider fixing your last attempt at implementing a `Catch` operator, and undelete your answer. – Theodor Zoulias Mar 15 '21 at 10:16
  • @TheodorZoulias You are right. But, sometimes installing packages is not easy. like working with Unity. So I prefer do it by myself if it is just a small piece of code. – shtse8 Mar 15 '21 at 10:27

10 Answers10

46
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; }
Stefan Steinegger
  • 63,782
  • 15
  • 129
  • 193
24

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

hdoghmen
  • 3,175
  • 4
  • 29
  • 33
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 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? – Pluc Sep 10 '15 at 19:09
  • 1
    @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. – Jon Skeet Sep 10 '15 at 19:10
6

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.

LeBaptiste
  • 1,156
  • 14
  • 20
5

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}));
}
THTP
  • 1,336
  • 11
  • 6
4

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.

esteewhy
  • 1,300
  • 13
  • 23
2

When dealing with LINQ you'll commonly find scenarios where your expression could produce undesired side effects. As Jon said, the best way to combat these sort of problems is to have utility methods your LINQ expression can use that will handle these gracefully and in a fashion that won't blow up your code. For example, I have a method I've had to use time to time which wraps a TryParse to tell me if something is a number. There are many other examples of course.

One of the limitations of the expression syntax is that there are a lot of things it can't do either gracefully or even at all without breaking execution out of the expression temporarily to handle a given scenario. Parsing a subset of items in an XML file is wonderful example. Try parsing a complex parent collection with child subsets from an XML file within a single expression and you'll soon find yourself writing several expression pieces that all come together to form the entire operation.

Nathan Taylor
  • 24,423
  • 19
  • 99
  • 156
2
/// <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);

shtse8
  • 1,092
  • 12
  • 20
  • 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 Mar 15 '21 at 08:09
  • Exceptions in `ThisMethodMayThrowExceptions(a)` aren't caught. – Gert Arnold Mar 15 '21 at 09:20
  • 1
    @GertArnold it will be caught exceptions in `ThisMethodMayThrowExceptions(a)` because this was run in `enumerator.Current` – shtse8 Mar 15 '21 at 09:21
  • 1
    Not in a test I did. Add a [mcve] to prove your point. – Gert Arnold Mar 15 '21 at 09:26
  • 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. – Gert Arnold Mar 15 '21 at 10:47
  • 1
    @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` for returning default value. – shtse8 Mar 15 '21 at 10:52
  • 1
    @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. – shtse8 Mar 15 '21 at 10:56
  • 1
    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 Mar 15 '21 at 11:17
  • 1
    You could also consider adding a `///` 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. – Theodor Zoulias Mar 15 '21 at 11:22
  • 1
    @TheodorZoulias Added and provided more overload for different scenarios. – shtse8 Mar 15 '21 at 11:29
1

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

  • FYI the library [System.Interactive](https://www.nuget.org/packages/System.Interactive/) includes also a [`Catch`](https://github.com/dotnet/reactive/blob/main/Ix.NET/Source/System.Interactive/System/Linq/Operators/Catch.cs) 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. – Theodor Zoulias Dec 12 '20 at 11:19
  • Thanks for your clarification! AsCatchable returns object of ICatchableEnumerable. 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). – Artem Prokudin Dec 13 '20 at 19:00
0

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
Rm558
  • 4,621
  • 3
  • 38
  • 43
0

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

}

Herman Schoenfeld
  • 8,464
  • 4
  • 38
  • 49