我有以下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。
答案 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循环体中迭代的集合。