有时检查不可重复的 IEnumerable
以查看它是否为空是有帮助的。 LINQ的Any
对此不起作用,因为它消耗了序列的第一个元素,例如
if(input.Any())
{
foreach(int i in input)
{
// Will miss the first element for non-repeatable sequences!
}
}
(注意:我知道在这种情况下不需要进行检查 - 这只是一个例子!真实世界的例子是针对右侧Zip
执行IEnumerable
可能是空的。如果它是空的,我希望结果是原样的左侧IEnumerable
。)
我想出了一个看起来像这样的潜在解决方案:
private static IEnumerable<T> NullifyIfEmptyHelper<T>(IEnumerator<T> e)
{
using(e)
{
do
{
yield return e.Current;
} while (e.MoveNext());
}
}
public static IEnumerable<T> NullifyIfEmpty<T>(this IEnumerable<T> source)
{
IEnumerator<T> 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<int> ReadNumbers()
{
for(;;)
{
int i;
if (int.TryParse(Console.ReadLine(), out i) && i != -1)
{
yield return i;
}
else
{
yield break;
}
}
}
基本上来自用户输入或流等的东西
编辑#2:
我需要澄清一点,我正在寻找一种保留IEnumerable
lazy 性质的解决方案 - 将其转换为列表或数组在某些情况下可以作为答案,但不是我在这里。 (现实世界的原因是IEnumerable
中的项目数量在我的情况下可能很大,并且重要的是不要将它们全部存储在内存中。)
答案 0 :(得分:3)
你也可以只读取第一个元素,如果它不是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<int?> Source = new List<int?>(){1,2,3,4,5,6};
private static IEnumerable<int?> 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。
答案 1 :(得分:2)
您无需复杂化。带有一个额外foreach
变量的常规bool
循环可以解决问题。
如果你有
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
是一个相当基本的功能,很容易重新实现,您的替换功能可以使用类似于上面的功能。
编辑:您可以考虑
public static class MyEnumerableExtensions
{
public static IEnumerable<TFirst> NotReallyZip<TFirst, TSecond>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TFirst> 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;
}
}
}
}
答案 2 :(得分:1)
如果枚举很长,这不是一个有效的解决方案,但这是一个简单的解决方案:
var list = input.ToList();
if (list.Count != 0) {
foreach (var item in list) {
...
}
}