7

linqクエリがあり、その結果をforeachループで繰り返します。

1つ目は、テーブルレイアウトパネルからコントロールのコレクションを取得するクエリです。次に、コレクションを繰り返し処理して、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();
}

上記は私が期待したようには機能しません(つまり、エラーをスローします)、コントロールの半分(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は、基になるコレクションが変更されたことを示すエラーを(予想どおりに)スローします。

なぜ最初の例も爆発しないのですか?

Iterators / IEnumerableには、ここでは理解できないことがあります。linqとforeachの内部で何が起こっているのかを誰かが理解するのを手伝ってくれるのではないかと思います。

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

4

3 に答える 3

10

なぜ最初の例も爆発しないのですか?

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

List<T>を介して列挙してアイテムを削除するときに発生する例外は、実際にはList<T>.Enumeratorタイプによって発生し、「汎用」言語機能ではありません。

ドキュメントに明示的に記載されているように、これはこのタイプの優れた機能であることに注意してください。List<T>.EnumeratorIEnumerator<T>

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

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

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

于 2012-07-19T17:34:28.717 に答える
2

したがって、私の最初のケースでは、foreachループの反復ごとにlinq式が再評価されていますか?または、linqはtablelayoutpanel.controlsコレクションからIEnumeratorを取得していますか?

このように起こっているかのように考えてください。これは、両方の場合に起こっていることです。

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

上記の擬似コードは、アイテムを削除しようとすると、基本的にstringListコレクションの列挙の途中にいることを示しています。リードが言っているのは、2番目のケースでは、の列挙子が、列挙stringListの途中で誰もそれをいじっていないことを確認しているということです。

彼は、あなたのコントロールの場合、コントロールの列挙子は喜んで進んでおり、誰かがそのデータをいじくり回しているかどうかを確認していないと言っています。

完全を期すために、次の2つの例を比較してください。

このクエリは、タッチさlettersれるたびに再評価されますletters。これには、foreachループにいる時間が含まれます。

    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); 
    } 

このクエリはToList()を使用します。これはすぐにを入力し、foreachループlettersのコレクションには触れなくなります。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); 
    } 
于 2012-07-19T20:48:40.983 に答える
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);
        }
于 2012-07-19T23:59:41.523 に答える