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:
using System;
using System.Linq;
using System.Xml.Linq;
class Program
{
static void Main()
{
var element = new XElement(
"root",
new XElement("parent", new XElement("child")),
new XElement("parent", new XElement("child"))
);
var children = element.Descendants("child");
foreach (var child in children.ToList())
{
child.Parent.ReplaceWith(child);
}
}
}
Without the ToList
call, a NullReferenceException
is thrown. With the ToList()
call, there's no exception. The exception is:
Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object.
at System.Xml.Linq.XContainer.<GetDescendants>d__39.MoveNext()
at Program.Main()
Basically, you're invalidating the query by modifying the tree while iterating over it. This is a bit like calling Add
or Remove
on a List<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 calling ReplaceWith
- 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 separate XElement
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...
See more on this question at Stackoverflow