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

C# 是否可以在LINQ查询中处理异常?,c#,.net,linq,exception,exception-handling,C#,.net,Linq,Exception,Exception Handling,例如: myEnumerable.Select(a => ThisMethodMayThrowExceptions(a)); 即使抛出异常,如何使其工作?与具有默认值的try-catch块一样,会引发异常 myEnumerable.Select(a => { try { return ThisMethodMayThrowExceptions(a)); } catch(Exception) { return def

例如:

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表达式“内联”放置。)

处理LINQ时,您通常会发现表达式可能会产生不希望的副作用。正如Jon所说,解决这类问题的最佳方法是使用LINQ表达式可以使用的实用方法,该方法将优雅地处理这些问题,并且不会破坏代码。例如,我有一个方法,我必须使用time-to-time包装一个TryParse来告诉我某个东西是否是一个数字。当然还有很多其他的例子


表达式语法的一个限制是,如果不临时中断表达式的执行以处理给定场景,它就无法优雅地完成很多事情,甚至根本无法完成这些事情。解析XML文件中的项目子集就是一个很好的例子。尝试在单个表达式中解析XML文件中包含子子集的复杂父集合,您很快就会发现自己编写了几个表达式片段,这些片段组合在一起构成整个操作。

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

from a in myEnumerable
select (new Func<myType>(() => {
    try
    {
        return ThisMethodMayThrowExceptions(a));
    }
    catch(Exception)
    {
        return defaultValue;
    }
}))();
来自myEnumerable中的
选择(新函数(()=>{
尝试
{
通过例外情况(a)返回此方法;
}
捕获(例外)
{
返回默认值;
}
}))();

虽然它也有“气味”,但这种方法有时也可用于运行表达式内部有副作用的代码。

如果您需要表达式而不是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;
    }
}
公共静态类ExpressionHelper
{
公共静态表达式TryDefaultExpression(表达式成功,TResult defaultValue)
{
var body=Expression.TryCatch(success.body,Expression.Catch(Expression.Parameter(typeof(Exception)),Expression.Constant(defaultValue,typeof(TResult)));
var lambda=Expression.lambda(body,success.Parameters);
返回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}));
}
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);

[测试]
公开无效测试()
{
var strings=newobject[]{“1”、“2”、“woot”、“3”,Guid.NewGuid()}.AsQueryable();
var ints=strings.Select(ExpressionHelper.TryDefaultExpression(x=>Convert.ToInt32(x),0));
IsTrue(ints.SequenceEqual(new[]{1,2,0,3,0}));
}

当我很快想要尝试/捕获
IEnumerable

用法

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();
}
公共无效测试()
{
列出CompletedProcess=初始可枚举
。选择try(x=>RiskyOperation(x))
.OnCaughtException(异常=>{{u logger.Error(异常);返回null;})
.Where(x=>x!=null)//筛选失败的
.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; }
    }
}
公共静态类OnCaughtExceptionExtension
{
公共静态IEnumerable SelectTry(此IEnumerable可枚举,Func选择器)
{
foreach(可枚举中的TSource元素)
{
选择tryresult returnedValue;
尝试
{
returnedValue=new SelectTryResult(元素,选择器(元素),null);
}
捕获(例外情况除外)
{
returnedValue=newselecttryresult(元素,默认值(TResult),ex);
}
收益返回值;
}
}
公共静态IEnumerable OnCaughtException(此IEnumerable enumerable,Func exceptionHandler)
{
返回可枚举的。选择(x=>x.CaughtException==null?x.Result:exceptionHandler(x.CaughtException));
}
公共静态IEnumerable OnCaughtException(此IEnumerable enumerable,Func exceptionHandler)
{
返回可枚举的。选择(x=>x.CaughtException==null?x.结果:exceptionHandler(x.Source,x.CaughtException));
}
公共类SelectTryResult
{
内部SelectTryResult(TSource源、TResult结果、异常)
{
来源=来源;
结果=结果;
CaughtException=异常;
}
公共TSource源{get;private set;}
公共TResult结果{get;private set;}
公共异常CaughtException{get;private set;}
}
}
通过使用
SkipOnException
扩展,我们最终可以更进一步,例如可以选择接受一个异常处理程序

我为此创建了small。它支持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();


你认为什么是滥用例外?在我的业务逻辑中,我有一种计算员工薪酬的方法。有很多事情可能会出错,比如员工没有设定工资。我做了一个自定义异常,我可以在我的控制器中抛出和捕获,并将其放入我的视图模型中。这太糟糕了?@Pluc:员工应该有固定的工资吗?如果是这样的话,它的缺失听起来很特别。如果这有点出乎意料,我可能不会使用异常来处理它。仅供参考,该库还包括一个操作符。您可能应该检查该运算符的重载,以确保在有人希望同时使用您的库和该库时不会出现歧义。谢谢您的澄清!AsCatchable返回ICatchableEnumerable的对象。爱
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);