我想创建一个跳过第一项的foreach
。我在其他地方看到,最简单的方法是使用myCollection.Skip(1)
,但我有一个问题:
.Skip()
上的MSDN文档描述了它“在序列中绕过指定数量的元素,然后返回剩余的元素。”这是否意味着调用
foreach(object i in myCollection.Skip(1))
{ ... }
每次.Skip(1)
迭代时,程序都必须执行foreach
吗?或者foreach
(有点像switch
)不需要对数组进行多次评估吗?
创建一个虚拟var _dummy = myCollection.Skip(1)
并对此进行迭代会更有效吗?
答案 0 :(得分:41)
我只是用这个
嘲笑你的代码foreach(var v in Enumerable.Range(1,10).Skip(1))
v.Dump();
这是IL生成的。
IL_0001: nop
IL_0002: ldc.i4.1
IL_0003: ldc.i4.s 0A
IL_0005: call System.Linq.Enumerable.Range
IL_000A: ldc.i4.1
IL_000B: call System.Linq.Enumerable.Skip//Call to Skip
IL_0010: callvirt System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator
IL_0015: stloc.1 // CS$5$0000
IL_0016: br.s IL_0026
IL_0018: ldloc.1 // CS$5$0000
IL_0019: callvirt System.Collections.Generic.IEnumerator<System.Int32>.get_Current
IL_001E: stloc.0 // v
IL_001F: ldloc.0 // v
IL_0020: call LINQPad.Extensions.Dump
IL_0025: pop
IL_0026: ldloc.1 // CS$5$0000
IL_0027: callvirt System.Collections.IEnumerator.MoveNext
IL_002C: stloc.2 // CS$4$0001
IL_002D: ldloc.2 // CS$4$0001
IL_002E: brtrue.s IL_0018
IL_0030: leave.s IL_0042
IL_0032: ldloc.1 // CS$5$0000
IL_0033: ldnull
IL_0034: ceq
IL_0036: stloc.2 // CS$4$0001
IL_0037: ldloc.2 // CS$4$0001
IL_0038: brtrue.s IL_0041
IL_003A: ldloc.1 // CS$5$0000
IL_003B: callvirt System.IDisposable.Dispose
IL_0040: nop
IL_0041: endfinally
正如您所看到的,Skip
只被调用一次。
等效的c#代码看起来像这样
IEnumerator<int> e = ((IEnumerable<int>)values).GetEnumerator();//Get the enumerator
try
{
int m;//This variable is here prior to c#5.0
while(e.MoveNext())
{//int m; is declared here starting from c#5.0
m = (int)(int)e.Current;
//Your code here
}
}
finally
{
if (e != null) ((IDisposable)e).Dispose();
}
考虑下面的代码,如果foreach在每次迭代时调用VeryLongRunningMethodThatReturnsEnumerable
那么这将是噩梦。语言设计中存在巨大缺陷。幸运的是,它没有那样做。
foreach(var obj in VeryLongRunningMethodThatReturnsEnumerable())
{
//Do something with that obj
}
答案 1 :(得分:26)
您应该了解foreach
的工作方式。这个foreach循环:
foreach(T t in GetSomeEnumerable())
DoSomethingWithT(t);
等同于此代码:
var e = GetSomeEnumerable().GetEnumerator();
try{
while(e.MoveNext()){
T t = (T)e.Current; // unless e is the generic IEnumerator<T>,
// in which case, there is no cast
DoSomethingWithT(t);
}
}finally{
if(e is IDisposable)
e.Dispose();
}
答案 2 :(得分:6)
拉出来它可能会变得更清晰。
var myCollection = new List<object>();
var skipped = myCollection.Skip(1);
foreach (var i in skipped) {
Console.WriteLine(i.ToString());
}
所以跳过的只是IEnumerable
foreach
现在列举的内容。
这就是IL在这种情况下的样子:
IL_0000: newobj System.Collections.Generic.List<System.Object>..ctor
IL_0005: stloc.0 // myCollection
IL_0006: ldloc.0 // myCollection
IL_0007: ldc.i4.1
IL_0008: call System.Linq.Enumerable.Skip
IL_000D: stloc.1 // skipped
IL_000E: ldloc.1 // skipped
IL_000F: callvirt System.Collections.Generic.IEnumerable<System.Object>.GetEnumerator
IL_0014: stloc.3 // CS$5$0000
IL_0015: br.s IL_0029
IL_0017: ldloc.3 // CS$5$0000
IL_0018: callvirt System.Collections.Generic.IEnumerator<System.Object>.get_Current
IL_001D: stloc.2 // i
IL_001E: ldloc.2 // i
IL_001F: callvirt System.Object.ToString
IL_0024: call System.Console.WriteLine
IL_0029: ldloc.3 // CS$5$0000
IL_002A: callvirt System.Collections.IEnumerator.MoveNext
IL_002F: brtrue.s IL_0017
IL_0031: leave.s IL_003D
IL_0033: ldloc.3 // CS$5$0000
IL_0034: brfalse.s IL_003C
IL_0036: ldloc.3 // CS$5$0000
IL_0037: callvirt System.IDisposable.Dispose
IL_003C: endfinally
您的代码的IL看起来很相似:
var myCollection = new List<object>();
foreach (var i in myCollection.Skip(1)) {
Console.WriteLine(i.ToString());
}
IL_0000: newobj System.Collections.Generic.List<System.Object>..ctor
IL_0005: stloc.0 // myCollection
IL_0006: ldloc.0 // myCollection
IL_0007: ldc.i4.1
IL_0008: call System.Linq.Enumerable.Skip <-- 1 Call to .Skip() outside the loop.
IL_000D: callvirt System.Collections.Generic.IEnumerable<System.Object>.GetEnumerator
IL_0012: stloc.2 // CS$5$0000
IL_0013: br.s IL_0027
IL_0015: ldloc.2 // CS$5$0000
IL_0016: callvirt System.Collections.Generic.IEnumerator<System.Object>.get_Current
IL_001B: stloc.1 // i
IL_001C: ldloc.1 // i
IL_001D: callvirt System.Object.ToString
IL_0022: call System.Console.WriteLine
IL_0027: ldloc.2 // CS$5$0000
IL_0028: callvirt System.Collections.IEnumerator.MoveNext
IL_002D: brtrue.s IL_0015
IL_002F: leave.s IL_003B
IL_0031: ldloc.2 // CS$5$0000
IL_0032: brfalse.s IL_003A
IL_0034: ldloc.2 // CS$5$0000
IL_0035: callvirt System.IDisposable.Dispose
IL_003A: endfinally
它仍然只有一个.Skip()调用。
答案 3 :(得分:4)
Skip
的整个表达式只会被调用一次。 Skip
使用延迟执行,以便在有不使用延迟执行的操作时执行。在那一刻,在背景上构建了一个表达式树,并且如果没有任何变化,则对IEnumerable
的实例的引用将返回给使用它的调用者。
答案 4 :(得分:2)
你的迭代是命令的结果:
myCollection.Skip(1)
这实际上返回了省略第一个元素的IEnumerable
类型的myCollection
。所以你的foreach就是反对缺少第一个元素的新IEnumerable
。 foreach
强制通过枚举对所产生的Skip(int)
方法进行实际评估(其执行推迟到枚举,就像其他LINQ方法,如Where
等)。它将是相同的为:
var mySkippedCollection = myCollection.Skip(1);
foreach (object i in mySkippedCollection)
...
以下是Skip(int)
实际上最终执行的代码:
private static IEnumerable<TSource> SkipIterator<TSource>(IEnumerable<TSource> source, int count)
{
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
while (count > 0 && enumerator.MoveNext())
{
count--;
}
if (count <= 0)
{
while (enumerator.MoveNext())
{
yield return enumerator.Current; // <-- here's your lazy eval
}
}
}
yield break;
}