9

考虑以下代码:

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(@"<Parts>
  <Part name=""DisappearsOk"" disabled=""true""></Part>
  <Part name=""KeepMe"" disabled=""false""></Part>
  <Part name=""KeepMe2"" ></Part>
  <Part name=""ShouldBeGone"" disabled=""true""></Part>  
</Parts>");

            XmlNode root = xmlDoc.DocumentElement;
            List<XmlNode> disabledNodes = new List<XmlNode>();

            try
            {

                foreach (XmlNode node in root.ChildNodes.Cast<XmlNode>()
                                             .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中运行此代码时,正如预期的那样,我没有遇到异常。我期待一个,因为我在迭代列表时从列表中删除了一些东西。

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

在此处输入图像描述

为什么我没有收到无效操作异常?

请注意,IDEOne.com 中的等效代码确实给出了预期的异常:http: //ideone.com/qoRBbb

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

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


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

我的问题是我在 VSexpress 中没有得到那个异常,但在 IDEOne 中得到了(我希望在两种/所有情况下都会出现这种情况,或者至少如果没有,我希望得到正确的结果)。


Wouter 的回答看来,当第一个孩子被删除时,它似乎使迭代器无效,而不是给出异常。有没有官方这么说的?在其他情况下是否会出现这种行为?我会调用以静默方式使迭代器无效,而不是使用“静默但致命”异常。

4

2 回答 2

2

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

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

var col = root.ChildNodes.Cast<XmlNode>()
                             .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);
}
于 2013-03-28T13:12:24.713 回答
2

即使是下面的代码也不会抛出任何异常:

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

它将只删除一个元素。我不是 100% 我的解释是正确的,但它是在正确的轨道上。当你遍历一个集合时,你检索它的枚举数。对于 XmlNode,它是一个集合,这是一个名为XmlChildEnumerator.

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

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

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

这不会引发异常,因为它不检查集合是否已更改,它只是想移动到它可以找到的下一个节点。但是由于被移除(分离)的节点不属于集合,所以循环停止。

希望这可以解决问题。

于 2013-03-28T13:55:08.493 回答