Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

foreach and linq query - need help trying to understand please

Tags:

c#

foreach

linq

I have a linq query, the results of which I iterate over in a foreach loop.

The first is a query which grabs a collection of controls from a table layout panel, I then iterate over the collection and remove the controls from the tableLayoutPanel thus:

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

The above does not do as I expected (i.e. Throw an error), it removes only half the controls (the ODD numbered ones) it appears that on each iteration the AllItems reduces it's self, and though the underlying collection is being modified no error is thrown.

If I do simmilar with an array of strings:

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

        }

This time Visual studio throws an error (as expected) complaining that the underlying collection has changed.

Why does the first example not blow up too?

There is something about Iterators/IEnumerable that I am not understanding here and I wonder if someone could help me understand what is going on under the hood with linq and foreach.

(I am aware that I can cure both issues by AllItems.ToList(); before I iterate, but would like to understand why the second example throws an error and the first does not)

like image 497
John Avatar asked Jul 19 '12 17:07

John


1 Answers

Why does the first example not blow up too?

The IEnumerator implementation within tableLayoutPanel1.Controls isn't performing the proper checks to see if the collection has been modified. This doesn't mean that it's a safe operation (as the results aren't appropriate), but rather that the class wasn't implemented in a way that raises an exception (as it probably should).

The exception that is raised when you enumerate through a List<T> and remove an item is actually raised by the List<T>.Enumerator type, and is not a "general purpose" language feature.

Note that this is a nice feature of the List<T>.Enumerator type, as the IEnumerator<T> documentation explicitly states:

An enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated and its behavior is undefined.

While there is no contract requiring an exception to be thrown, it's beneficial for implementations to do so when you're entering the realm of "undefined behavior."

In this case, the TableLayoutControlCollection class enumerator implementation is not performing the extra checks, so you're seeing what happens when you use a feature where the "behavior is undefined." (In this case, it's removing every other control.)

like image 118
Reed Copsey Avatar answered Sep 21 '22 00:09

Reed Copsey