示例:
myEnumerable.Select(a => ThisMethodMayThrowExceptions(a));
如果它抛出异常,如何使其工作?就像带有默认值情况的try catch块一样,抛出异常......
答案 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();