Is it possible to handle exceptions within LINQ queries?
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.
Jader Dias
Perl, Javascript, C#, Go, Matlab and Python Developer
Updated on October 29, 2021Comments
-
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 over 8 yearsWhat 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 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 over 3 yearsFYI 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 over 3 yearsThanks 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 about 3 yearsYet another version of Select + a try/catch method block. All answers here do that. But worse, this method doesn't do anything.
-
Gert Arnold about 3 yearsExceptions in
ThisMethodMayThrowExceptions(a)
aren't caught. -
shtse8 about 3 years@GertArnold it will be caught exceptions in
ThisMethodMayThrowExceptions(a)
because this was run inenumerator.Current
-
Gert Arnold about 3 yearsNot in a test I did. Add a minimal reproducible example to prove your point.
-
Gert Arnold about 3 yearsYet, 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 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 andFunc<T>
for returning default value. -
shtse8 about 3 years@GertArnold for right approach, some people like
Linq
, while some people like foreach and thencatch
, or even some people likeReactive
. There is no absolute right approach, it depends. For me, if the project useLinq
most, please use allLinq
. if the project likefor
, then please do all infor
. if most areReactive
, please useReactive
for all. -
Theodor Zoulias about 3 yearsI would prefer to access the
enumerator.Current
inside the tryblock
. It is highly unlikely to throw, but who knows how a custom enumerator could behave? -
Theodor Zoulias about 3 yearsYou could also consider adding a
///<summary></summary>
on top of theCatch
operator, to document its behavior. It's not totally obvious from its name that the operator omits yielding the failed elements. -
shtse8 about 3 years@TheodorZoulias Added and provided more overload for different scenarios.