user316117
user316117

Reputation: 8271

Manually iterating over a selection of XML elements (C#, XDocument)

What is the “best practice” way of manually iterating (i.e., one at a time with a “next” button) over a set of XElements in my XDocument? Say I select the set of elements I want thusly:

var elems = from XElement el in m_xDoc.Descendants()
            where (el.Name.LocalName.ToString() == "q_a") 
            select el;

I can use an IEnumerator to iterate over them, i.e., IEnumerator m_iter;

But when I get to the end and I want to wrap around to the beginning if I call Reset() on it, it throws a NotSupportedException. That’s because, as the Microsoft C# 2.0 Specification under chapter 22 "Iterators" says "Note that enumerator objects do not support the IEnumerator.Reset method. Invoking this method causes a System.NotSupportedException to be thrown ."

So what IS the right way of doing this? And what if I also want to have bidirectional iteration, i.e., a “back” button, too?

Someone on a Microsoft discussion forum said I shouldn’t be using IEnumerable directly anyway. He said there was a way to do what I want with LINQ but I didn’t understand what. Someone else suggested dumping the XElements into a List with ToList(), which I think would work, but I wasn’t sure it was “best practice”. Thanks in advance for any suggestions!

Upvotes: 3

Views: 2447

Answers (2)

Philip Daubmeier
Philip Daubmeier

Reputation: 14934

The solution is very simple. Just create a List out of your XElements collection.

var elems = (from XElement el in m_xDoc.Descendants()
            where (el.Name.LocalName.ToString() == "q_a") 
            select el).ToList();

You can enumerate through it via the indexer elems[i] and jump back and forth. Just store the current index in a variable, and decrement/increment it on a button click (with wrap-around).

The xml you have is parsed on demand by your linq query (see MSDN for deferred execution and lazy evaluation in Linq to XML). Even if it would support IEnumerable.Reset(), it would have to parse it again every time. If you call .ToList<T>() it parses all descendant elements once and loads them into the memory.

Upvotes: 1

Marc Gravell
Marc Gravell

Reputation: 1062800

It is very rare you need to use the enumerator directly; just use foreach on elems. Here's iterating it twice:

// first time
foreach(var item in elems) {...}
// second time
foreach(var item in elems) {...}

No need for Reset() - it simply uses GetEnumerator() twice for you, which is the correct way of doing it. If you can't run the query twice for whatever reason, or want random access rather than sequential, then you'll have to buffer it - perhaps into a list with ToList().

Upvotes: 1

Related Questions