为什么相同的LINQ表达式在两个不同的foreach循环中表现不同?

时间:2013-03-28 12:09:11

标签: c# .net linq linq-to-xml

我有以下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循环时,这次我使用List<T>方法将LINQ表达式的结果转换为ToList()(正如@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()使LINQ表达式按预期在foreach循环中工作? LINQ结果在两种不同情况下表现不同的任何技术原因?

我使用的是.NET 4.0。

2 个答案:

答案 0 :(得分: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不会改变它迭代的集合。

答案 1 :(得分: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循环体中迭代的集合。