开发者

C# Modification of IEnumerable while Enumerating with ForEach

This is something that I was exploring to see if I could take what was

List<MdiChild> openMdiChildren = new List<MdiChild>();
foreach(child in MdiManager.Pages)
{
    openMdiChildren.Add(child);
}

foreach(child in openMdiChild)
{
   child.Close();
}

and shorten it to not require 2 foreach loops.

Note I've changed what the objects are called to simplify this for this example (these come from 3rd party controls). But for information and understanding MdiManager.Pages inherits form CollectionBase, which in turn inherits IEnumerable

and MdiChild.Close() removes the open child from the MdiManager.Pages Collection, thus altering the collection and causing the enumeration to throw an exception if the collection was modified during enumeration, e.g..

foreach(child in MdiManage.Pages)
{
   child.Close();
}

I was able to the working double foreach to

((IEnumerable) MdiManager.Pages).Cast<MdiChild>.ToList()
.ForEach(new Action<MdiChild>(c => c.Close());

Why does this not have the same issues dealing wi开发者_开发技巧th modifying the collection during enumeration? My best guess is that when Enumerating over the List created by the ToList call that it is actually executing the actions on the matching item in the MdiManager.Pages collection and not the generated List.

Edit

I want to make it clear that my question is how can I simplify this, I just wanted to understand why there weren't issues with modifying a collection when I performed it as I have it written currently.


Your call to ToList() is what saves you here, as it's essentially duplicating what you're doing above. ToList() actually creates a List<T> (a List<MdiChild> in this case) that contains all of the elements in MdiManager.Pages, then your subsequent call to ForEach operates on that list, not on MdiManager.Pages.

In the end, it's a matter of style preference. I'm not personally a fan of the ForEach function (I prefer the query composition functions like Where and ToList() for their simplicity and the fact that they aren't engineered to have side-effects upon the original source, whereas ForEach is not).

You could also do:

foreach(child in MdiManager.Pages.Cast<MdiChild>().ToList())
{
    child.Close();
}

Fundamentally, all three approaches do exactly the same thing (they cache the contents of MdiManager.Pages into a List<MdiChild>, then iterate over that cached list and call Close() on each element.


When you call the ToList() method you're actually enumerating the MdiManager.Pages and creating a List<MdiChild> right there (so that's your foreach loop #1). Then when the ForEach() method executes it will enumerate the List<MdiChild> created previously and execute your action on each item (so that's foreach loop #2).

So essentially it's another way of accomplishing the same thing, just using LINQ.


You could also write it as:

foreach(var page in MdiManager.Pages.Cast<MdiChild>.ToList())
    page.Close();

In any case, when you call ToList() extension method on an IEnumerable; you are creating a brand new list. Deleted from its source collection ( in this case, MdiManager.Pages ) will not affect the list output by ToList().

This same technique can be used to delete elements from a source collection without worrying about affecting the source enumerable.


You're mostly right.

ToList() creates a copy of the enumeration, and therefore you are enumerating the copy.

You could also do this, which is equivalent, and shows what you are doing:

var copy = new List<MdiChild>(MdiManager.Pages.Cast<MdiChild>());

foreach(var child in copy)
{
    child.Close();
}

Since you are enumerating the elements of the copy enumeration, you don't have to worry about modifying the Pages collection, since each object referece that existed in the Pages collection now also exists in copy and changes to Pages don't affect it.

All the remaining methods on the call, ForEach() and the casts, are superfluous and can be eliminated.


At first glance, the culprit is ToList(), which is a method returning a copy of the items as a List, thus circumventing the problem.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜