44

foreach最初の項目をスキップするを作成したい。これを行う最も簡単な方法は を使用することだと他の場所で見たことがありますmyCollection.Skip(1)が、質問があります。

MSDN のドキュメントに.Skip()は、「シーケンス内の指定された数の要素をバイパスし、残りの要素を返す」と記載されています。これは、への呼び出しを意味しますか?

foreach(object i in myCollection.Skip(1))
{ ... }

プログラムは反復する.Skip(1)たびに実行する必要がありますか? foreachそれとも、配列の複数回の評価を必要としませforeachswitchか?

var _dummy = myCollection.Skip(1)代わりにダミーを作成してこれを繰り返す方が効率的でしょうか?

4

5 に答える 5

50

私はこれであなたのコードを嘲笑しました

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
}
于 2013-11-08T19:33:17.977 に答える
33

仕組みを理解する必要があります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();
}
于 2013-11-08T19:46:01.790 に答える
7

それを引き出すと、おそらくより明確になります。

var myCollection = new List<object>();
var skipped = myCollection.Skip(1);

foreach (var i in skipped) {
    Console.WriteLine(i.ToString());
}

したがって、スキップされたのは、現在列挙されている ですIEnumerableforeach

その場合の 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() 呼び出しが 1 つしかありません。

于 2013-11-08T19:29:56.963 に答える
4

with 式全体Skipが一度だけ呼び出されます。Skip遅延実行を使用しないアクションがあると実行されるように、遅延実行を使用します。その瞬間、式ツリーがバックグラウンドで構築され、インスタンスへの参照が、IEnumerable何も変化がなければそれを使用する呼び出し元に返されます。

于 2013-11-08T19:39:59.183 に答える
2

あなたの反復は、コマンドの結果です:

myCollection.Skip(1)

これは、最初の要素を省略しIEnumerableた型の を効果的に返します。myCollectionしたがって、あなたの foreach はIEnumerable、最初の要素が欠けている new に反対しています。は、列挙によって生成されたメソッド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;
}
于 2013-11-08T19:35:41.600 に答える