LINQ查询中是否可以处理异常?

时间:2009-08-18 14:29:15

标签: c# .net linq exception exception-handling

示例:

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

如果它抛出异常,如何使其工作?就像带有默认值情况的try catch块一样,抛出异常......

8 个答案:

答案 0 :(得分:37)

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

但实际上,它有一些气味。

关于lambda语法:

x => x.something

是一种捷径,可以写成

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

答案 1 :(得分:18)

调用具有try / catch的投影:

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

...

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

不可否认,我很少想要使用这种技术。一般来说,这就像滥用例外一样,但有时会有API让你别无选择。

(我几乎肯定会把它放在一个单独的方法中,而不是把它作为lambda表达式“内联”。)

答案 2 :(得分:5)

如果您需要Expression而不是lambda函数(例如从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}));
}

答案 3 :(得分:5)

当我想快速尝试/捕捉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扩展来更进一步,例如接受可选的异常处理程序。

答案 4 :(得分:4)

Stefan的理解语法解决方案的变体:

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

虽然它也“闻起来”,但是这种方法有时仍可用于运行带有副作用的代码。

答案 5 :(得分:2)

在处理LINQ时,您通常会发现表达式会产生不良副作用的情况。正如Jon所说,解决这类问题的最佳方法是使用LINQ表达式可以使用的实用方法,这些方法可以优雅地处理这些方法,并且不会破坏代码。例如,我有一个方法,我必须时间使用,包装TryParse告诉我,如果有什么是数字。当然还有很多其他的例子。

表达式语法的一个限制是,有许多事情要么不能优雅地执行,要么甚至根本不会暂时从表达式中执行来处理给定的场景。解析XML文件中的项目子集就是一个很好的例子。尝试使用单个表达式中的XML文件中的子子集解析复杂的父集合,您很快就会发现自己编写了几个表达式,它们组合在一起形成整个操作。

答案 6 :(得分: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;
    }
}

用法:

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

答案 7 :(得分:1)

我为此创建了一个小library。它支持Select,SelectMany和Where运算符的异常处理。 用法示例:

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

对等的

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

还可以处理多种类型的异常

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