安全地检查不可重复的IEnumebles是否空虚

有时候检查一个不可重复的 IEnumerable以查看它是否为空是有帮助的。 LINQ的Any对此不起作用,因为它消耗了序列的第一个元素,例如

 if(input.Any()) { foreach(int i in input) { // Will miss the first element for non-repeatable sequences! } } 

(注意:我知道在这种情况下没有必要进行检查 – 这只是一个例子!真实世界的例子是对一个可能为空的右手IEnumerable执行一个Zip 。如果它是空的,我希望结果是原样的左手IEnumerable 。)

我想出了一个看起来像这样的潜在解决方案:

 private static IEnumerable NullifyIfEmptyHelper(IEnumerator e) { using(e) { do { yield return e.Current; } while (e.MoveNext()); } } public static IEnumerable NullifyIfEmpty(this IEnumerable source) { IEnumerator e = source.GetEnumerator(); if(e.MoveNext()) { return NullifyIfEmptyHelper(e); } else { e.Dispose(); return null; } } 

然后可以按如下方式使用:

 input = input.NullifyIfEmpty(); if(input != null) { foreach(int i in input) { // Will include the first element. } } 

我有两个问题:

1)这是否合理? 从性能的角度来看,它可能会出现问题吗? (我猜不是,但值得问。)

2)是否有更好的方法来实现相同的最终目标?


编辑#1:

这是一个不可重复的IEnumerable的例子,以澄清:

 private static IEnumerable ReadNumbers() { for(;;) { int i; if (int.TryParse(Console.ReadLine(), out i) && i != -1) { yield return i; } else { yield break; } } } 

基本上,来自用户输入或流等的东西。

编辑#2:

我需要澄清一点,我正在寻找一种能够保留IEnumerable 惰性的解决方案 – 在某些情况下将其转换为列表或数组可以是一个答案,但这不是我在此之后所做的。 (真实的原因是IEnumerable的项目数量在我的情况下可能很大,并且重要的是不要将它们全部存储在内存中。)

你不需要复杂化它。 一个带有一个额外bool变量的常规foreach循环就可以了。

如果你有

 if(input.Any()) { A foreach(int i in input) { B } C } 

并且您不想读取input两次,您可以将其更改为

 bool seenItem = false; foreach(int i in input) { if (!seenItem) { seenItem = true; A } B } if (seenItem) { C } 

根据B作用,您可以完全避免使用seenItem变量。

在你的情况下, Enumerable.Zip是一个相当基本的function,很容易重新实现,你的替换function可以使用类似于上面的东西。

编辑 :你可能会考虑

 public static class MyEnumerableExtensions { public static IEnumerable NotReallyZip(this IEnumerable first, IEnumerable second, Func resultSelector) { using (var firstEnumerator = first.GetEnumerator()) using (var secondEnumerator = second.GetEnumerator()) { if (secondEnumerator.MoveNext()) { if (firstEnumerator.MoveNext()) { do yield return resultSelector(firstEnumerator.Current, secondEnumerator.Current); while (firstEnumerator.MoveNext() && secondEnumerator.MoveNext()); } } else { while (firstEnumerator.MoveNext()) yield return firstEnumerator.Current; } } } } 

你也可以只读取第一个元素,如果它不是null,则将第一个元素与输入的其余部分连接起来:

 var input = ReadNumbers(); var first = input.FirstOrDefault(); if (first != default(int)) //Assumes input doesn't contain zeroes { var firstAsArray = new[] {first}; foreach (int i in firstAsArray.Concat(input)) { // Will include the first element. Console.WriteLine(i); } } 

对于正常的可枚举,第一个元素将重复两次,但对于不可重复的可枚举,它将起作用,除非不允许迭代两次。 此外,如果您有这样的调查员:

 private readonly static List Source = new List(){1,2,3,4,5,6}; private static IEnumerable ReadNumbers() { while (Source.Count > 0) { yield return Source.ElementAt(0); Source.RemoveAt(0); } } 

然后它将打印:1,1,2,3,4,5,6。原因是第一个元素在返回之后被消耗。 因此第一个枚举器停在第一个元素上,从来没有机会消耗第一个元素。 但这是一个写得很糟糕的调查员的案例。 如果元素被消耗,则返回…

 while (Source.Count > 0) { var returnElement = Source.ElementAt(0); Source.RemoveAt(0); yield return returnElement; } 

…你得到的预期输出为:1,2,3,4,5,6。

如果枚举很长,这不是一个有效的解决方案,但它是一个简单的解决方案:

 var list = input.ToList(); if (list.Count != 0) { foreach (var item in list) { ... } }