Операции Ancestors и AncestorsAndSelf

30

Ancestors

Операция Ancestors может быть вызвана на последовательности узлов и возвращает последовательность, содержащую элементы-предки каждого исходного узла. Операция Ancestors имеет два прототипа, описанные ниже:

Первый прототип Ancestors
public static IEnumerable<XElement> Ancestors<T> (
   this IEnumerable<T> source
   ) where T : XNode

Эта версия операции может быть вызвана на последовательности узлов или объектов, унаследованных от XNode. Она возвращает последовательность элементов, содержащую элементы-предки каждого узла исходной последовательности source.

Второй прототип Ancestors
public static IEnumerable<XElement> Ancestors<T> ( 
    this IEnumerable<T> source, 
    XName name 
    ) where T : XNode

Эта версия подобна первой, за исключением того, что ей передается имя, и в выходной последовательности возвращаются только элементы-предки, соответствующие указанному имени.

Ниже представлен пример вызова первого прототипа Ancestors:

XDocument xDoc = new XDocument(
             new XElement("Employees",
               new XElement("Employee",
                 new XAttribute("type", "Programmer"),
                 new XElement("FirstName", "Alex"), new XElement("LastName", "Erohin")),
               new XElement("Employee",
                 new XAttribute("type", "Editor"),
                 new XElement("FirstName", "Elena"), new XElement("LastName", "Volkova"))));

            // Получаем всех потомков XML-дерева с помощью операции Descendants
            IEnumerable<XElement> elements = xDoc.Element("Employees").Descendants("FirstName");

            // Сначала отобразим исходные элементы
            foreach (XElement e in elements)
                Console.WriteLine("Исходный элемент: {0} : значение = {1}",e.Name,e.Value);

            // Теперь отобразим элементы предки
            foreach (XElement e in elements.Ancestors())
                Console.WriteLine("Элемент-предок: " + e.Name);

В приведенном примере сначала создается документ XML. Затем генерируется последовательность элементов FirstName. Вспомните, что этот метод Ancestors вызывается на последовательности узлов, а не отдельном узле, поэтому нужна последовательность, на которой можно будет вызвать его.

Поскольку требуется возможность отобразить имена узлов в целях идентификации, в действительности строится последовательность элементов, потому что элементы имеют имена, а узлы — нет. Затем проводится перечисление по последовательности с отображением исходных элементов — просто чтобы можно было увидеть исходную последовательность. После этого перечисляются элементы, возвращенные методом Ancestors, с отображением каждого из них. Результат выглядит так:

Пример вызова первого прототипа Ancestors

Как видите, в выводе присутствуют два элемента исходной последовательности — два элемента FirstName. Затем представлены предки каждого из этих двух элементов.

Таким образом, с помощью операции Ancestors можно извлекать все элементы-предки для каждого узла в последовательности. В данном случае это последовательность элементов, что вполне нормально, поскольку элемент наследуется от узла. Вспомните, что не следует путать операцию Ancestors, вызванную на последовательности узлов, которая была продемонстрирована здесь, с методом Ancestors, описанным ранее.

В этом виде пример не столь выразителен, как мог бы быть, поскольку код расширен в демонстрационных целях. Например, нужно было захватить последовательной элементов FirstName, чтобы отобразить их наглядно в выводе. То есть оператор, содержащий вызов метода Descendants, и последующий блок foreach предназначены для этой цели. Затем во втором цикле foreach вызывается операция Ancestors и отображается каждый элемент-предок. В действительности, во втором цикле foreach можно было бы вызвать на каждом элементе последовательности элементов FirstName метод Ancestors, не прибегая к операции Ancestors.

Однако, благодаря операции Ancestor и лаконичности LINQ, этот запрос можно комбинировать в единственный более краткий оператор, как показано в примере ниже:

foreach (XElement e in xDoc.Element("Employees").Descendants("FirstName").Ancestors())
      Console.WriteLine("Элемент предок: " + e.Name);

В этом примере сразу вызывается операция Ancestors на последовательности элементов, возвращенных методом Descendants. Поэтому метод Descendants возвращаем последовательность элементов, а операция Ancestors вернет последовательность элементов, содержащих всех предков каждого элемента в последовательности, на которой она вызвана.

Поскольку этот код призван быть более кратким, он не отображает элементов FirstName, как это делалось в предыдущем примере. Тем не менее, элементу предки должны быть теми же самыми. Давайте в этом убедимся:

Более краткий пример вызова первого прототипа Ancestors

Так и есть! В коде реального приложения, скорее всего, будет выбран более компактный запрос, вроде только что продемонстрированного.

Для демонстрации использования второго прототипа Ancestors применяется тот же базовый код, что и в первом пример, но вызов операции Ancestors изменяется так, что она будет включать параметр Employee, чтобы можно было получить только элементы с соответствующим именем. Код представлен ниже:

...
foreach (XElement e in xDoc.Element("Employees").Descendants("FirstName").Ancestors("Employee"))
       Console.WriteLine("Элемент предок: " + e.Name);

Теперь результаты должны включать только элементы Employee и, конечно же, исходные элементы, но два элемента Employees, которые были показаны в примере с первым прототипом, теперь должны исчезнуть:

Вызов второго прототипа Ancestors

Так и произошло.

AncestorsAndSelf

Операция AncestorsAndSelf может быть вызвана на последовательности элементов и возвращает последовательность, которая включает элементы-предки каждого исходного элемента, а также его самого. Эта операция подобна операции Ancestors за исключением того, что она может быть вызвана только на элементах, а не на узлах, и также включает исходные элементы в возвращенную последовательность элементов-предков.

Операция AncestorsAndSelf имеет два прототипа, описанные ниже:

Первый прототип AncestorsAndSelf
public static IEnumerable<XElement> AncestorsAndSelf ( 
      this IEnumerable<XElement> source
)

Эта версия операции может быть вызвана на последовательности элементов и возвращает последовательность элементов, включающих каждый исходный элемент и всех его предков.

Второй прототип AncestorsAndSelf
public static IEnumerable<XElement> AncestorsAndSelf ( 
       this IEnumerable<XElement> source,
       XName name
)

Эта версия подобна первой, за исключением дополнительного параметра — имени, и возвращает в выходной последовательности только те элементы и его предков, которые соответствуют указанному имени.

Для демонстрации первого прототипа AncestorsAndSelf используется тот же базовый пример, что применялся для первого прототипа Ancestors, только на этот раз вместо Ancestors вызывается операция AncestorsAndSelf:

XDocument xDoc = new XDocument(
             new XElement("Employees",
               new XElement("Employee",
                 new XAttribute("type", "Programmer"),
                 new XElement("FirstName", "Alex"), new XElement("LastName", "Erohin")),
               new XElement("Employee",
                 new XAttribute("type", "Editor"),
                 new XElement("FirstName", "Elena"), new XElement("LastName", "Volkova"))));

            // Получаем всех потомков XML-дерева с помощью операции Descendants
            IEnumerable<XElement> elements = xDoc.Element("Employees").Descendants("FirstName");

            // Сначала отобразим исходные элементы
            foreach (XElement e in elements)
                Console.WriteLine("Исходный элемент: {0} : значение = {1}",e.Name,e.Value);

            Console.WriteLine();

            // Теперь отобразим элементы предки
            foreach (XElement e in elements.AncestorsAndSelf())
                Console.WriteLine("Элемент-предок: " + e.Name);

Как и в первом прототипе Ancestors, сначала создается документ XML. Затем генерируется последовательность элементов FirstName. Вспомните, что этот метод AncestorsAndSelf вызывается на последовательности элементов, а не на отдельном элементе, так что понадобилась последовательность, на которой его вызвать. Затем эта последовательность перечисляется с отображением элементов. После этого перечисляются элементы, возвращенные методом AncestorsAndSelf, и также отображаются.

Если все работает, как ожидалось, то результат должен быть таким же, как и в примере прототипа Ancestors, за исключением того, что элементы FirstName также будут включены в вывод. И вот результат:

Пример вызова первого прототипа AncestorsAndSelf

Для демонстрации второго прототипа AncestorsAndSelf используется тот же базовый пример, что и при демонстрации второго прототипа Ancestors, разумеется, с заменой вызова Ancestors на AncestorsAndSelf:

...
foreach (XElement e in elements.AncestorsAndSelf("Employee"))
            Console.WriteLine("Элемент-предок: " + e.Name);

Теперь должны быть получены только элементы по имени Employee. Вот результат:

Вызов второго прототипа AncestorsAndSelf

Обратите внимание, что в выводе метода AncestorsAndSelf теперь содержатся только элементы Employee, потому что только они соответствуют переданному имени. В этом случае даже не получены исходные элементы, поскольку их имена не соответствуют. Так что функция работает должным образом.

Этот прототип операции кажется довольно бесполезным. Сколько уровней одноименных элементов планируется иметь в дереве XML? Если вы не отведете минимум два, то каким образом этот метод сможет вернуть сами исходные элементы вместе с предками? Это кажется маловероятным.

Пройди тесты
Лучший чат для C# программистов