Ручной повтор выбора элементов XML (C#, XDocument)

Каков «лучший способ» ручной итерации (т. е. по одному с кнопкой «Далее») по набору XElements в моем XDocument? Скажем, я выбираю набор элементов, которые хочу, таким образом:

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

Я могу использовать IEnumerator для их перебора, т. е. IEnumerator m_iter;

Но когда я дохожу до конца и хочу перейти к началу, если я вызываю для него Reset(), он выдает исключение NotSupportedException. Это связано с тем, что, как сказано в Спецификации Microsoft C# 2.0 в главе 22 «Итераторы», «Обратите внимание, что объекты перечислителя не поддерживают метод IEnumerator.Reset. Вызов этого метода вызывает исключение System.NotSupportedException».

Итак, каков правильный способ сделать это? А что, если я также хочу иметь двунаправленную итерацию, то есть кнопку «назад»?

Кто-то на дискуссионном форуме Microsoft сказал, что мне все равно не следует использовать IEnumerable напрямую. Он сказал, что есть способ сделать то, что я хочу, с помощью LINQ, но я не понял, какой. Кто-то еще предложил выгрузить XElements в список с помощью ToList(), что, я думаю, сработает, но я не был уверен, что это «лучшая практика». Спасибо заранее за любые предложения!


person user316117    schedule 27.04.2010    source источник


Ответы (2)


Решение очень простое. Просто создайте список из своей коллекции XElements.

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

Вы можете просмотреть его через индексатор elems[i] и прыгать туда и обратно. Просто сохраните текущий индекс в переменной и уменьшите/увеличьте его нажатием кнопки (с переносом).

Имеющийся у вас xml анализируется по запросу вашим запросом linq (см. MSDN для отложенного выполнения и отложенное вычисление в Linq to XML). Даже если бы он поддерживал IEnumerable.Reset(), ему приходилось бы каждый раз анализировать его заново. Если вы вызываете .ToList<T>(), он один раз анализирует все элементы-потомки и загружает их в память.

person Philip Daubmeier    schedule 27.04.2010

Непосредственное использование перечислителя требуется очень редко; просто используйте foreach на elems. Вот итерация дважды:

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

Нет необходимости в Reset() — он просто использует GetEnumerator() дважды для вас, что является правильным способом сделать это. Если вы не можете выполнить запрос дважды по какой-либо причине или хотите случайный доступ, а не последовательный, вам придется буферизовать его - возможно, в список с ToList().

person Marc Gravell    schedule 27.04.2010
comment
Я думаю, что цикл foreach - это не то, что ищет оператор. Он хочет получить следующий элемент, только если пользователь нажмет кнопку. Я думаю, что ему лучше всего хранить данные в списке. - person Philip Daubmeier; 28.04.2010