使用 ForEach 枚举时对 IEnumerable 的 C# 修改
这是我正在探索的事情,看看我是否可以将原来的内容
List<MdiChild> openMdiChildren = new List<MdiChild>();
foreach(child in MdiManager.Pages)
{
openMdiChildren.Add(child);
}
foreach(child in openMdiChild)
{
child.Close();
}
缩短到不需要 2 个 foreach
循环。
注意 我已经更改了对象的名称以简化此示例(这些来自第 3 方控件)。但为了信息和理解 MdiManager.Pages
继承表单 CollectionBase
,而 CollectionBase
又继承 IEnumerable
,并且 MdiChild.Close()
删除打开的子项来自 MdiManager.Pages
集合,从而更改集合并导致枚举在枚举期间修改集合时引发异常,例如。
foreach(child in MdiManage.Pages)
{
child.Close();
}
我能够工作双 foreach
到
((IEnumerable) MdiManager.Pages).Cast<MdiChild>.ToList()
.ForEach(new Action<MdiChild>(c => c.Close());
为什么这在处理枚举期间修改集合时没有相同的问题?我最好的猜测是,当枚举由 ToList 调用创建的列表时,它实际上是对 MdiManager.Pages
集合中的匹配项执行操作,而不是对生成的列表执行操作。
编辑
我想澄清的是,我的问题是如何简化它,我只是想了解为什么当我按照我当前编写的方式执行修改集合时没有问题。
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 understandingMdiManager.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 with 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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
您对
ToList()
的调用可以拯救您,因为它本质上是重复您上面所做的事情。ToList()
实际上创建了一个List
(在本例中为List
),其中包含中的所有元素>MdiManager.Pages
,那么您对 ForEach
的后续调用将在该列表上运行,而不是在MdiManager.Pages
上运行。归根结底,这是风格偏好的问题。我个人并不喜欢
ForEach
函数(我更喜欢Where
和ToList()
等查询组合函数,因为它们简单且事实上,它们的设计不会对原始源产生副作用,而ForEach
则不然)。您还可以这样做:
从根本上讲,所有三种方法都执行完全相同的操作(它们将
MdiManager.Pages
的内容缓存到List
中,然后迭代缓存的内容列出每个元素并调用Close()
。Your call to
ToList()
is what saves you here, as it's essentially duplicating what you're doing above.ToList()
actually creates aList<T>
(aList<MdiChild>
in this case) that contains all of the elements inMdiManager.Pages
, then your subsequent call toForEach
operates on that list, not onMdiManager.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 likeWhere
andToList()
for their simplicity and the fact that they aren't engineered to have side-effects upon the original source, whereasForEach
is not).You could also do:
Fundamentally, all three approaches do exactly the same thing (they cache the contents of
MdiManager.Pages
into aList<MdiChild>
, then iterate over that cached list and callClose()
on each element.当您调用
ToList()
方法时,您实际上是在枚举MdiManager.Pages
并在那里创建一个List
(所以这就是你的foreach
循环 #1)。然后,当ForEach()
方法执行时,它将枚举之前创建的List
并对每个项目执行操作(这就是foreach
循环#2)。所以本质上这是完成同样事情的另一种方式,只是使用 LINQ。
When you call the
ToList()
method you're actually enumerating theMdiManager.Pages
and creating aList<MdiChild>
right there (so that's yourforeach
loop #1). Then when theForEach()
method executes it will enumerate theList<MdiChild>
created previously and execute your action on each item (so that'sforeach
loop #2).So essentially it's another way of accomplishing the same thing, just using LINQ.
您也可以将其写为:
在任何情况下,当您在 IEnumerable 上调用 ToList() 扩展方法时;您正在创建一个全新的列表。从其源集合(在本例中为 MdiManager.Pages )中删除不会影响 ToList() 的列表输出。
同样的技术可用于从源集合中删除元素,而不必担心影响源可枚举。
You could also write it as:
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.
你基本上是对的。
ToList()
创建枚举的副本,因此您正在枚举该副本。您也可以执行此操作,这是等效的,并显示您正在执行的操作:
由于您正在枚举
copy
枚举的元素,因此您不必担心修改Pages< /code> 集合,因为
Pages
集合中存在的每个对象引用现在也存在于copy
中,并且对Pages
的更改不会影响它。调用中的所有剩余方法、
ForEach()
和强制转换都是多余的,可以消除。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:
Since you are enumerating the elements of the
copy
enumeration, you don't have to worry about modifying thePages
collection, since each object referece that existed in thePages
collection now also exists incopy
and changes toPages
don't affect it.All the remaining methods on the call,
ForEach()
and the casts, are superfluous and can be eliminated.乍一看,罪魁祸首是 ToList(),这是一种将项目的副本作为列表返回的方法,从而规避了该问题。
At first glance, the culprit is ToList(), which is a method returning a copy of the items as a List, thus circumventing the problem.