0

我有以下 XML。

<Parts>
  <Part name="Part1" disabled="true"></Part>
  <Part name="Part2" disabled="false"></Part>
  <Part name="Part3" ></Part>
  <Part name="Part4" disabled="true"></Part>  
</Parts>

我想删除disabled属性设置为的节点true。如果 'disabled' 属性没有用于任何 'Part' 元素,则意味着它没有被禁用。

我写了以下代码:

XmlNode root = xmlDoc.DocumentElement;
List<XmlNode> disabledNodes = new List<XmlNode>();
foreach(XmlNode node in root.ChildNodes)
{
    if(node.Attributes["disabled"] != null && 
        Convert.ToBoolean(node.Attributes["disabled"].Value))
    {
        disabledNodes.Add(node);
    }
}

foreach (XmlNode node in disabledNodes)
{
    root .RemoveChild(node);
}

此代码按预期从 XML 中删除 2 个节点。

然后我编写了以下代码以使代码紧凑:

foreach (XmlNode node in root.ChildNodes.Cast<XmlNode>()
    .Where(child => child.Attributes["disabled"] != null && 
    Convert.ToBoolean(child.Attributes["disabled"].Value)))
{
    root.RemoveChild(node); // This line works fine without any exception.
}

我发现这个循环只迭代一次,只从 XML 中删除一个节点。


编辑问题:

现在,当我更改foreach循环时,这次我将 LINQ 表达式的结果转换为List<T>usingToList()方法(正如@Toni Petrina 在他的回答中所建议的那样)。这次它工作正常!

 foreach (XmlNode node in root.ChildNodes.Cast<XmlNode>()
        .Where(child => child.Attributes["disabled"] != null && 
        Convert.ToBoolean(child.Attributes["disabled"].Value)).ToList())
    {
        root.RemoveChild(node); // This line works fine without any exception.
    }

为什么使用ToList()made LINQ表达式foreach按预期循环工作?为什么 LINQ 的结果在两种不同情况下表现不同的任何技术原因?

我正在使用 .NET 4.0。

4

2 回答 2

3

您的问题是您在枚举时更改了集合。这是错误的。你应该使用这样的东西:

var disabledNodes = root.ChildNodes.Cast<XmlNode>()
    .Where(child => child.Attributes["disabled"] != null && 
    Convert.ToBoolean(child.Attributes["disabled"].Value)).ToArray();

foreach (XmlNode node in disabledNodes)
{
    root.RemoveChild(node);
}

更新

这是由于延迟执行。如果不使用 ToArray() 或 ToList(),IEnumerator 会在需要下一个元素时(即 foreach 转到下一轮时)一个接一个地返回值。当 foreach 执行第一轮时,您的源代码会更改并且迭代停止。但是如果你调用 ToArray(),你会得到一个包含 disabledNodes 数组的新变量,并且 foreach 不会改变它迭代的集合。

于 2013-03-28T12:20:37.243 回答
0

写:

foreach (XmlNode node in root.ChildNodes.Cast<XmlNode>()
    .Where(child => child.Attributes["disabled"] != null && 
    Convert.ToBoolean(child.Attributes["disabled"].Value)).ToList())
{
    root.RemoveChild(node);
}

我添加了额外的 ToList() 来强制立即执行 LINQ 表达式。

当你创建一个 LINQ 查询时,你会得到一个 IEnumerable 集合,它实际上并不包含任何结果。即使您编写了所有这些 Select 和 Where 以及许多其他子句,但在开始迭代之前不会执行完整的查询。只有这样才能运行实际的查询。

在原始代码中,您创建了一个查询并开始对其进行迭代。您收到了通过所有 LINQ 子句的第一个项目并删除了第一个节点。但是由于您正在迭代现在已修改的根集合,因此迭代停止。

您不能在 foreach 循环的主体中更改您正在迭代的集合。

于 2013-03-28T12:14:04.117 回答