In these statements (running with MoreLinq
):
var xml = @"
<div>
<p>
<h2>hey</h2>
</p>
<pre />
<h2 class=""cool"" />
<p>
<h2>okay</h2>
</p>
</div>
".Trim();
var div = XElement.Parse(xml);
var h2Elements = div.Descendants("h2");
h2Elements.ToList().ForEach(i =>
{
if(i.Parent.Name != "p") return;
i.Parent.ReplaceWith(i);
});
I see that i.Parent.ReplaceWith(i)
does not throw an exception but this will throw a null-reference exception (using ForEach
from MoreLinq
):
h2Elements.ForEach(i =>
{
if(i.Parent.Name != "p") return;
i.Parent.ReplaceWith(i);
});
I understand that LINQ's ToList()
is making a copy of the list but would not the copy just throw an exception as well? Also, is there a memory leak happening here with some kind of orphaned references?
You don't need MoreLINQ to demonstrate this at all - and you can simplify the sample code, too:
Without the
ToList
call, aNullReferenceException
is thrown. With theToList()
call, there's no exception. The exception is:Basically, you're invalidating the query by modifying the tree while iterating over it. This is a bit like calling
Add
orRemove
on aList<T>
while iterating over it, but it's harder for LINQ to XML to spot the problem and throw a meaningful exception. It's important to note that the exception doesn't come when callingReplaceWith
- it's the iteration part that's failing, as it can't find traverse the tree properly after you've modified it.When you call
ToList()
, you're just getting separateXElement
values in a list - when you iterate over that list, any changes to the elements won't change the references that appear in the list.As for a memory leak: nope, that's what the garbage collector is for...