Подтвердить что ты не робот

Можно ли обрабатывать исключения в запросах LINQ?

Пример:

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

Как заставить его работать, даже если он выдает исключения? Как и блок catch catch со значением по умолчанию, исключаются исключения...

4b9b3361

Ответ 1

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

Но на самом деле он имеет некоторый запах.

О синтаксисе лямбда:

x => x.something

является своего рода ярлыком и может быть записан как

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

Ответ 2

Вызвать проекцию, которая имеет попытку/улов:

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

...

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

По общему признанию, я редко хотел бы использовать эту технику. Это похоже на злоупотребление исключениями в целом, но иногда есть API, которые не оставляют вам выбора.

(я почти наверняка поместил бы его в отдельный метод, вместо того, чтобы ставить его "inline" как выражение лямбда, хотя.)

Ответ 3

Вариант решения Стефана для синтаксиса понимания:

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

Хотя, он также "пахнет", но этот подход иногда может использоваться для запуска кода с внутренними выражениями побочных эффектов.

Ответ 4

При работе с LINQ вы обычно найдете сценарии, в которых ваше выражение может вызвать нежелательные побочные эффекты. Как сказал Джон, лучшим способом борьбы с такими проблемами является использование методов утилиты, которые может использовать выражение LINQ, которое будет обрабатывать эти изящно и таким образом, чтобы не взорвать ваш код. Например, у меня есть метод, который я должен был использовать время от времени, который обертывает TryParse, чтобы сказать мне, если что-то есть число. Конечно, есть много других примеров.

Одно из ограничений синтаксиса выражений состоит в том, что есть много вещей, которые он не может сделать либо изящно, либо даже вообще не нарушать выполнение из выражения временно для обработки данного сценария. Замечательный пример - синтаксический анализ подмножества элементов в XML файле. Попробуйте разобрать сложную родительскую коллекцию с дочерними подмножествами из XML файла в одном выражении, и вскоре вы обнаружите, что пишете несколько частей выражения, которые все объединяются, чтобы сформировать всю операцию.

Ответ 5

Если вам нужно выражение вместо лямбда-функции (например, при выборе из IQueryable), вы можете использовать что-то вроде этого:

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

Использование:

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

Ответ 6

У меня есть небольшое расширение, когда я быстро хочу попробовать/поймать каждую итерацию IEnumerable<T>

Использование

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

Расширение

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

В конце концов мы могли бы пойти немного дальше, имея расширение SkipOnException, допустив, например, обработчик исключений.