0

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?

babayi
  • 85
  • 4
  • I've always checked the return value of `reader.Read()` to find the end of the file, but I don't know if it _should_ be "more reliable" (I've never checked the `EOF` property tbh). – C.Evenhuis Apr 28 '16 at 17:32
  • That would be a logical thing to do but in this case I cannot because of: http://stackoverflow.com/questions/36909789/why-is-the-xmlreader-skipping-elements – babayi Apr 28 '16 at 17:36
  • You could use `do { ... } while reader.Read();` instead, if you want. – C.Evenhuis Apr 28 '16 at 18:08

1 Answers1

1

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.