LINQ查询执行投射,跳过或包装exception,其中源抛出IEnumerable.GetNext()

我想要一个通用的解决方案,但作为一个例子,假设我有一个IEnumerable ,其中一些可以解析为整数,有些则不能。

 var strings = new string[] { "1", "2", "notint", "3" }; 

显然,如果我做了Select(s => int.Parse(s, temp))它会在枚举时抛出exception。

在这种情况下,我可以先.All(s => int.TryParse(s, out temp)) ,但我想要一个通用的解决方案,我不必枚举IEnumerable两次。

理想情况下,我希望能够执行以下操作,它调用我的魔术exception跳过方法:

 // eg parsing strings var strings = new string[] { "1", "2", "notint", "3" }; var numbers = strings.Select(s => int.Parse(s)).SkipExceptions(); // eg encountering null object var objects = new object[] { new object(), new object(), null, new object() } var objecttostrings = objects.Select(o => o.ToString()).SkipExceptions(); // eg calling a method that could throw var myClassInstances = new MyClass[] { new MyClass(), new MyClass(CauseMethodToThrow:true) }; var myClassResultOfMethod = myClassInstances.Select(mci => mci.MethodThatCouldThrow()).SkipExceptions(); 

如何编写SkipExceptions()扩展方法?


SelectSkipExceptions()方法的一些很好的答案,但是我想知道是否可以创建SkipExceptions()方法,与AsParallel()

怎么样(你可能想给这个特殊的选择扩展名一个更好的名字)

 public static IEnumerable SelectIgnoringExceptions( this IEnumerable values, Func selector) { foreach (var item in values) { TOutput output = default(TOutput); try { output = selector(item); } catch { continue; } yield return output; } } 

编辑5添加了一个using语句,感谢评论中的建议

  public static IEnumerable SkipExceptions( this IEnumerable values) { using(var enumerator = values.GetEnumerator()) { bool next = true; while (next) { try { next = enumerator.MoveNext(); } catch { continue; } if(next) yield return enumerator.Current; } } } 

但是,这依赖于尚未创建的传入IEnumerable(因此已经抛出exception)作为前面Function的列表。 例如,如果您这样调用它可能不起作用:选择(..)。ToList()。SkipExceptions()

创建一个返回NullableTryParseInt方法:

 int? TryParseInt(string s) { int i; if (int.TryParse(s, out i)) return i; return null; } 

并在您的查询中使用它:

 var numbers = strings.Select(s => TryParseInt(s)) .Where(i => i.HasValue) .Select(i => i.Value); 

另见Bill Wagner的这篇文章 ,它提出了一个非常类似的案例。


现在,我不认为你可以编写类似于通用SkipExceptions方法的东西,因为你会太晚捕获exception,它会结束Select循环……但你可能会写一个SelectSkipException方法:

 public static IEnumerable SelectSkipExceptions( this IEnumerable source, Func selector) { if (source == null) throw new ArgumentNullException("source"); if (selector == null) throw new ArgumentNullException("selector"); return source.SelectSkipExceptionsIterator(selector); } private static IEnumerable SelectSkipExceptionsIterator( this IEnumerable source, Func selector) { foreach(var item in source) { TResult value = default(TResult); try { value = selector(item); } catch { continue; } yield return value; } } 

即使是接受的答案也可能不够“一般”。 如果有一天你发现你需要知道发生了什么exception怎么办?

以下扩展名

 static class EnumeratorHelper { //Don't forget that GetEnumerator() call can throw exceptions as well. //Since it is not easy to wrap this within a using + try catch block with yield, //I have to create a helper function for the using block. private static IEnumerable RunEnumerator(Func> generator, Func onException) { using (var enumerator = generator()) { if (enumerator == null) yield break; for (; ; ) { //You don't know how to create a value of T, //and you don't know weather it can be null, //but you can always have a T[] with null value. T[] value = null; try { if (enumerator.MoveNext()) value = new T[] { enumerator.Current }; } catch (Exception e) { if (onException(e)) continue; } if (value != null) yield return value[0]; else yield break; } } } public static IEnumerable WithExceptionHandler(this IEnumerable orig, Func onException) { return RunEnumerator(() => { try { return orig.GetEnumerator(); } catch (Exception e) { onException(e); return null; } }, onException); } } 

会有所帮助。 现在您可以添加SkipExceptions

  public static IEnumerable SkipExceptions(this IEnumerable orig){ return orig.WithExceptionHandler(orig, e => true); } 

通过使用不同的onException回调,您可以执行不同的操作

  • 打破迭代但忽略exception: e => false
  • 尝试继续迭代: e => true
  • 记录exception等

这是一个小型的完整程序,用于展示灵感来自monad的答案。 您可能想要更改“Maybe”类的名称,因为它受到启发而不是实际上是其他语言中定义的“Maybe”。

 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TestMaybe { class Program { static void Main(string[] args) { var strings = new string[] { "1", "2", "notint", "3" }; var ints = strings.Select(s => new Maybe(s, str => int.Parse(str))).Where(m => !m.nothing).Select(m => m.value); foreach (var i in ints) { Console.WriteLine(i); } Console.ReadLine(); } } public class Maybe { public readonly bool nothing; public readonly T2 value; public Maybe(T1 input, Func map) { try { value = map(input); } catch (Exception) { nothing = true; } } } } 

编辑:根据代码的需要,如果map(input)的结果为null,您可能也不希望nothing设置为true

这与Thomas的答案相同,但有一个lambda和LINQ表达式。 托马斯+1。

 Func tryParse = s => { int? r = null; int i; if (int.TryParse(s, out i)) { r = i; } return r; }; var ints = from s in strings let i = tryParse(s) where i != null select i.Value; 

您可以将Where和Select方法链接在一起。

 var numbers = strings.Where(s => { int i; return int.TryParse(s, out i); }).Select(int.Parse); 

使用Where方法有效地消除了编写自己的SkipExceptions方法的需要,因为这基本上就是你正在做的事情。