Search code examples
c#.netxmlreaderyield-return

Why is the XmlReader getting closed when returning an IEnumerable<T>?


I have the following extension methods:

public static IEnumerable<XElement> GetElement(this XmlReader reader, string elementName)
{    
    reader.MoveToElement();

    while (!reader.EOF)
    {
        if (reader.NodeType == XmlNodeType.Element 
            && reader.Name.Equals(elementName))
        {
            yield return XNode.ReadFrom(reader) as XElement;
        }

        reader.Read();
    }
}

public static IEnumerable<XElement> ExtractElement(this Stream stream, string elementName)
{    
    using (var reader = XmlReader.Create(stream))
    {
        return reader.GetElement(elementName));
    }
}

I then try to open a FileStream and get a specific element in an XML file using:

using (var stream = File.Open("My.xml", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
    var element = stream.ExtractElement("subscriptionForms").First();
    element.Elements("subscriptionForm").Count().ShouldBe(11);
}

However running the code results in an infinite loop due to the xml reader getting closed (reader.EOF being false and reader.Read() not doing anything) but when I change the following line:

return reader.GetElement(elementName));

to:

foreach (var xElement in reader.GetElement(elementName, ignoreCase))
{
    yield return xElement;
}

everything seems to be working fine. Why is the former implementation causing the reader to close?


Solution

  • reader.GetElement(elementName) does not start enumerating yet. It returns an object that, once you look at what it contains, starts reading from reader. Since you return that object directly, and the return statement is wrapped in a using, the using statement causes the reader to be closed before the enumeration starts.

    When you wrap it in a foreach loop, the using statement's effect of closing the reader is delayed until the foreach terminates, and by that time it'll be okay, by that time you no longer need the reader.