Even the following code won't throw any exceptions:
foreach (XmlNode node in root.ChildNodes)
root.RemoveChild(node);
And it will remove exactly one element. I am not 100% that my explanation is correct, but it is on the right track. When you iterate over a collection, you retrieve its enumerator. For XmlNode, which is a collection, this is a custom class called XmlChildEnumerator
.
If you would look up the MoveNext implementation via Reflector, you would see that the enumerator remembers the node it is currently looking at. When you call MoveNext, you move onto the next sibling.
What happens in the code above is that you get the first node from the collection. Enumerator implicitly generated in the body of the foreach loop takes that first node as its current node. Then, in the body of the foreach loop you remove that node.
Now that node is detached from the list and the execution moves onto calling MoveNext again. However, since we have just removed the first node from the collection, it is detached from the collection and node has no sibling. Since there is no sibling for the node, iteration stops and foreach loop exits, thus removing only single element.
This doesn't throw exception since it doesn't check if the collection has been changed, it just wants to move on to the next node it can find. But since the removed (detached) node doesn't belong to a collection, the loop stops.
Hope this clears up the issue.