是否有可能在LINQ查询中处理异常?

问题描述:

例子:是否有可能在LINQ查询中处理异常?

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

如何让即使它引发异常工作的呢?就像一个带有默认值的try catch块抛出异常...

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

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

关于lambda语法:

x => x.something 

是怎样的一个快捷方式,并可以写成

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

呼叫其具有的try/catch投影:

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

... 

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

无可否认我会很少想使用这种技术。这感觉就像是一般的滥用例外情况,但有时候会有API让你别无选择。

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

+0

你认为是什么滥用异常?在我的业务逻辑中,我有一种计算员工薪酬的方法。有很多事情可能会出现问题,例如员工没有工资。我做了一个自定义的异常,我可以把它放在我的控制器中并放入我的视图模型中。那很糟? – Pluc 2015-09-10 19:09:28

+0

@Pluc:员工*是否意味着*有薪水?如果是这样,它缺少声音例外。如果有点期待,我可能不会使用异常来处理它。 – 2015-09-10 19:10:29

当LINQ打交道,你会发现通常情况下在您的表达可能会产生不希望副作用。正如乔恩所说,解决这些问题的最好方法是使用LINQ表达式可以使用的实用方法,这些方法可以优雅地处理这些问题,而且不会破坏代码。例如,我有一个方法,我不得不使用时间来包装一个TryParse,告诉我是否有数字。当然还有很多其他的例子。

表达式语法的局限性之一是它有许多事情不能正常执行,甚至根本不能执行超出表达式的临时表达式来处理给定的场景。解析XML文件中的项目子集是一个很好的例子。尝试使用单个表达式中的XML文件解析复杂的父集合和子集合,然后您很快就会发现自己编写了几个表达部分,这些表达部分聚集在一起形成整个操作。

斯特凡对理解语法的解决方案的一个变化:

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

虽然,它“闻香”过,但还是有时可以用于里面表达的副作用运行的代码这种方法。

如果您需要表达式而不是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})); 
} 

我都设有一个小扩展名,当我赶紧想尝试/捕获一个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扩展,接受可选例如异常处理程序。