views:

412

answers:

2

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!

+1  A: 

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.

Philip Daubmeier
+1  A: 

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().

Marc Gravell
I think the `foreach` loop isnt what the op is searching for. He wants to get the next element only if the user pushes a button. I think he is best off storing the data into a list.
Philip Daubmeier