Linq,XmlNodes,foreach和例外

请考虑以下代码:

using System; using System.Collections.Generic; using System.Linq; using System.Xml; using System.Xml.Linq; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { XmlDocument xmlDoc = new XmlDocument(); xmlDoc.LoadXml(@"     "); XmlNode root = xmlDoc.DocumentElement; List disabledNodes = new List(); try { foreach (XmlNode node in root.ChildNodes.Cast() .Where(child => child.Attributes["disabled"] != null && Convert.ToBoolean(child.Attributes["disabled"].Value))) { Console.WriteLine("Removing:"); Console.WriteLine(XDocument.Parse(node.OuterXml).ToString()); root.RemoveChild(node); } } catch (Exception Ex) { Console.WriteLine("Exception, as expected"); } Console.WriteLine(); Console.WriteLine(XDocument.Parse(root.OuterXml).ToString()); Console.ReadKey(); } } } 

当我在visual studio express 2010中运行此代码时,我没有像预期的那样得到exception。 我期待一个,因为我在迭代它时从列表中删除了一些东西。

我得到的是一个列表,只删除了第一个子节点:

在此处输入图像描述

为什么我没有得到无效的操作exception?

请注意,IDEOne.com中的等效代码确实提供了预期的exception : http : //ideone.com/qoRBbb

另请注意,如果我删除所有LINQ( .Cast().Where() )我得到相同的结果,只删除了一个节点,没有exception。

我在VSExpress中的设置有问题吗?


请注意,我知道延迟执行是涉及的,但我希望where子句在迭代时迭代源枚举(子注释),这将给出我期望的exception。

我的问题是我在VSexpress中没有得到那个例外,但在IDEOne中做了(我希望在两个/所有情况下,或者至少如果没有,我希望得到正确的结果)。


从Wouter的回答来看,当第一个子节点被移除时,它似乎使迭代器无效,而不是给出exception。 有什么官方说的吗? 在其他情况下会出现这种情况吗? 我会默默地使迭代器失效,而不是“无声但致命”的exception。

即使以下代码也不会抛出任何exception:

 foreach (XmlNode node in root.ChildNodes) root.RemoveChild(node); 

它将删除恰好一个元素。 我不是100%,我的解释是正确的,但它是在正确的轨道上。 迭代集合时,将检索其枚举器。 对于XmlNode,它是一个集合,这是一个名为XmlChildEnumerator的自定义类。

如果您要通过Reflector查找MoveNext实现,您会看到枚举器会记住它当前正在查看的节点。 当您调用MoveNext时,您将移动到下一个兄弟。

上面的代码中发生的是你从集合中获得第一个节点。 在foreach循环体中隐式生成的枚举器将第一个节点作为其当前节点。 然后,在foreach循环的主体中删除该节点。

现在该节点与列表分离,执行再次调用MoveNext。 但是,由于我们刚从集合中删除了第一个节点,因此它与集合分离,并且节点没有兄弟节点。 由于节点没有兄弟节点,迭代停止并且foreach循环退出,因此仅删除单个元素。

这不会抛出exception,因为它不会检查集合是否已更改,它只是想继续查找它可以找到的下一个节点。 但由于删除(分离)节点不属于集合,因此循环停止。

希望这能解决问题。

因为您正在迭代ChildNodes ,所以删除第一个子项会使迭代器失效。 因此,迭代将在第一次删除后停止。

如果您拆分过滤和迭代,您的代码将删除所有项目:

 var col = root.ChildNodes.Cast() .Where(child => child.Attributes["disabled"] != null && Convert.ToBoolean(child.Attributes["disabled"].Value)).ToList(); foreach (XmlNode node in col) { Console.WriteLine("Removing:"); Console.WriteLine(XDocument.Parse(node.OuterXml).ToString()); root.RemoveChild(node); }