2012-07-19 5 views
7

私はlinqクエリを持っています。その結果はforeachループで繰り返されます。foreachとlinq query - わかりやすいヘルプが必要です

最初は、私は、コレクションにわたって反復し、従ってtableLayoutPanelからコントロールを削除し、テーブルレイアウトパネルからコントロールのコレクションをつかむクエリである:上記

var AllItems = (from Item in this.tableLayoutPanel1.Controls.OfType<ItemControl>() 
       select Item); 

foreach (ItemControl item in AllItems) 
{ 
    Trace.WriteLine("Removing " + item.ToString()); 
    this.tableLayoutPanel1.Controls.Remove(item); 
    item.Dispose(); 
} 

Iとして行いません(つまり、エラーをスローする)、コントロールの半分(ODD番号のもの)を削除します 各繰り返しでAllItemsが自己を減らし、元のコレクションが変更されていてもエラーはスローされません。

私は文字列の配列でsimmilarない場合:

 string[] strs = { "d", "c", "A", "b" }; 
     List<string> stringList = strs.ToList(); 

     var allitems = from letter in stringList 
         select letter; 

     foreach (string let in allitems) 
     { 
      stringList.Remove(let); 

     } 

今回のVisual Studioは、基になるコレクションが変更されたことを訴えて(予想通り)エラーがスローされます。

なぜ最初の例もあまりにも吹き飛ばされませんか?

私はここで理解していないですし、誰かが私はLINQとのforeachとフードの下で何が起こっているか理解するのに役立つことができれば、私は疑問に思うことをIEnumerableをイテレータ/についての何かがあります。

(私は)AllItems.ToList(によって両方の問題を治すことができることを承知しています。私は、反復する前に、2番目の例では、エラーをスローし、最初にはない理由を理解したいと思います)

答えて

10

なぜ最初の例はあまりにも爆発しないのですか? tableLayoutPanel1.Controls

IEnumerator実装は、コレクションが変更されているかどうかを確認するために、適切なチェックを行っていません。これは安全な操作(結果が適切でないため)ではなく、例外を発生させるようにクラスが実装されていないことを意味します(おそらくそうであるはずです)。

List<T>で列挙してアイテムを削除するときに発生する例外は、実際にはList<T>.Enumerator型で発生し、「汎用」言語機能ではありません。 IEnumerator<T>ドキュメントが明示的に述べて、これは、便利な機能List<T>.Enumeratorタイプのであることを

注:

列挙子はコレクションが変わらない限り有効です。要素の追加、変更、削除など、コレクションに変更が加えられた場合、列挙子は回復不能に無効化され、その動作は未定義です。

例外がスローされる必要はありませんが、実装では「未定義の動作」の領域に入るときに実装すると有益です。

この場合、TableLayoutControlCollectionクラスの列挙子の実装では余分なチェックが行われないため、「動作が未定義」の機能を使用するとどうなるかがわかります。 (この場合、他のすべてのコントロールが削除されます。)

+0

ので、シーケンスは、常にすべての列挙に変更し、 'Enumerable.OfType'は遅延実行を使用して実装されているという事実に関連していないこの問題の原因ですか? –

+2

@TimSchmelterさて、OfType は、ソース列挙子を直接使用して列挙します。チェックはしていませんが、オブジェクトを列挙してチェックをループ内に置いても同じことが起こります。基になる列挙子は、コントロールコレクションが変更されているかどうかのチェックを実行していません。 –

+0

@ TimSchmelter:元のポストコードからすべてのLINQ操作を削除できると思われますが、まったく同じように動作します。 Reedの言うとおり、これは列挙型の実装の結果です。 OfTypeはそれとは関係ありません。 – StriplingWarrior

2

私の第1のケースでは、linch式はすべてforeachループの 繰り返しで評価されますか?またはLinqがIEnumeratorを tablelayoutpanel.controlsコレクションから取得していますか?上記の擬似コードを使用すると、アイテムを削除しようとして、あなたが本質的であることを示して

foreach (string l in (from letter in stringList select letter)) 
    { 
     stringList.Remove(l); 
    } 

:両方のあなたのケースで何が起こっているかであることを考えることは、このように起こったかのように

stringListコレクションの列挙の途中です。 Reedが言っているのは、2番目のケースでは、stringListの列挙子が、列挙の途中で誰もそれを乱していないことを確認しているということです。

あなたのコントロールの場合、コントロールの列挙子はうまくいっていて、誰かがそのデータで猿を見ているかどうかをチェックしていないと言っています。 lettersのためこのクエリは、我々は、foreachループ内にある時間が含まれlettersをタッチするたびに再評価されます

IEnumerable<string> letters = from letter in stringList select letter); 

    // everytime we hit this we're going to hit the stringList collection 
    foreach (string l in letters) 
    { 
     stringList.Remove(l); 
    } 

このクエリが使用する

完全性については

は、これら2つの例を比較しますToList()はすぐにlettersを埋め込み、foreachループのstringListコレクションにはもう触れません。

List<string> letters = (from letter in stringList select letter).ToList() 

    // because we ran ToList(), we will no longer enumerate stringList 
    foreach (string l in letters) 
    { 
     stringList.Remove(l); 
    } 
+0

ありがとうBrad。分かりました。 @everyone who responded:ありがとう、これは本当に私の心の中でこの問題を解決しました。あなたたち最高! – John

0

これを試してみてください:

var AllItems = (from Item in this.tableLayoutPanel1.Controls.OfType<ItemControl>() 
       select Item); 


    int totalCount = AllItems .Count; 
    for (int i = 0; i < totalCount; i++) 
     { 
      AllItems .RemoveAt(0); 
     } 
関連する問題