JavaRush /Курсы /C# SELF /LINQ to XML: XDocument

LINQ to XML: XDocument, XElement

C# SELF
48 уровень , 3 лекция
Открыта

1. Введение

Появление LINQ стало причиной революции в работе с XML в .NET. Если раньше для изменения XML приходилось писать громоздкие конструкции с NodeList и XmlElement, теперь мы можем выражать все операции в стиле LINQ. Это похоже на то, как если бы вы перешли на быструю скоростную автомагистраль после долгого плутания по гравийным дорогам.

LINQ to XML решает три главные задачи:

  • Упрощает синтаксис построения и чтения XML.
  • Делает навигацию по документу похожей на работу с коллекциями.
  • Позволяет интегрировать XML с любым LINQ-запросом.

Основные классы: XDocument, XElement, XAttribute

Прежде чем бросаться в глубокие воды LINQ to XML, познакомимся с тремя китами:

  • XDocument — это корень всего документа, аналог бумажного корешка, в котором всё содержится.
  • XElement — любой отдельный узел (элемент) XML. Это как отдельная коробка в складе, в которой могут быть другие коробки или товары (элементы, атрибуты и текст).
  • XAttribute — атрибут элемента, например, <User id="42" />.

Визуальная схема


+-----------------+
|   XDocument     |---------------------+
+-----------------+                     |
            |                      (Root XElement)
            v                           |
      +-------------+              +-----------------+
      |   XElement  |<-------------| XElement (root) |
      +-------------+              +-----------------+
            |                              |
        (XAttribute)                (nested XElements)

2. Чтение XML с помощью LINQ to XML

Давайте представим, что в нашем учебном приложении (например, адресная книга) мы хотим прочитать контакты из XML-файла. Пусть структура файла такая:

<Contacts>
  <Person id="1">
    <Name>Иван Иванов</Name>
    <Email>ivan@example.com</Email>
    <Phones>
      <Phone type="mobile">+12 999 123-45-67</Phone>
      <Phone type="home">+12 812 123-45-67</Phone>
    </Phones>
  </Person>
  <Person id="2">
    <Name>Мария Петрова</Name>
    <Email>maria@example.com</Email>
    <Phones>
      <Phone type="mobile">+12 921 123-45-67</Phone>
    </Phones>
  </Person>
</Contacts>

Читаем XML в память

Сначала добавим в проект директиву:

using System.Xml.Linq;

Теперь читаем файл или строку:

// Пусть xmlString содержит XML-документ, либо можно использовать XDocument.Load("contacts.xml")
var doc = XDocument.Parse(xmlString);

// Получаем список Person
var persons = doc.Root.Elements("Person");

foreach (var person in persons)
{
    string name = person.Element("Name")?.Value ?? "Без имени";
    string email = person.Element("Email")?.Value ?? "Без e-mail";
    string id = person.Attribute("id")?.Value ?? "нет id";
    
    Console.WriteLine($"Имя: {name}, Email: {email}, id: {id}");
}

- doc.Root даёт корневой элемент (Contacts).
- Elements("Person") возвращает все <Person>.
- Дальше берём нужные элементы/атрибуты через .Element("...") или .Attribute("...").
- Оператор ?. защищает от ошибок, если элемент/атрибут не найден.

Аналогия

Работать с XElement — как разбирать матрёшку: достаёшь одну, внутри есть другие, а там ещё и так далее. Только теперь это делается одним лёгким запросом, а не громоздкими обходами.

3. Составляем LINQ-запросы по XML

Хотим получить список всех мобильных телефонов всех людей? Пожалуйста, это можно сделать в стиле LINQ!

var mobilePhones = doc
    .Descendants("Phone") // ищем все Phone на любом уровне
    .Where(p => (string)p.Attribute("type") == "mobile")
    .Select(p => p.Value);

foreach (string phone in mobilePhones)
{
    Console.WriteLine($"Мобильный: {phone}");
}

- Descendants("Phone") ищет все теги <Phone> в документе.
- Attribute("type") — получаем атрибут.
- Приведение (string) к атрибуту автоматически даёт строку или null.

Крутые трюки

Можно искать сразу по нескольким уровням, не заботясь о сложной вложенности. Такое не получится сделать просто с помощью простого XmlDocument без мороки.

4. Создание и модификация XML "на лету"

XML удобно создавать прямо в памяти с помощью конструкторов XElement и XDocument. Вот пример, как добавить нового человека в наш документ (и, вспомним, это часть нашего "развивающегося приложения-справочника"):

// Создаём новый Person
var newPerson = new XElement("Person",
    new XAttribute("id", "3"),
    new XElement("Name", "Сергей Новиков"),
    new XElement("Email", "sergey@example.com"),
    new XElement("Phones",
        new XElement("Phone", new XAttribute("type", "work"), "+12 812 987-65-43")
    )
);

// Добавляем к корню
doc.Root.Add(newPerson);

// Сохраняем обратно в файл (или строку)
doc.Save("contacts.xml");

Комментарии:

- Можно вкладывать элементы с помощью "матрёшечных" конструкторов: первый аргумент — тэг, остальные — вложенное содержимое (могут быть элементы, атрибуты, текст).
- Атрибуты всегда идут до непосредственного содержимого (т.е. <Phone type="work">...).

5. Взаимодействие с коллекциями C# — магия LINQ

Часто нужно сериализовать обычную коллекцию объектов (например, List<Person>) в XML или наоборот. Делается это очень просто, практически одной строчкой LINQ:

var people = new List<Person>
{
    new Person { Id = 1, Name = "Иван Иванов", Email = "ivan@example.com" },
    // и т.д.
};

var doc = new XDocument(
    new XElement("Contacts",
        people.Select(p =>
            new XElement("Person",
                new XAttribute("id", p.Id),
                new XElement("Name", p.Name),
                new XElement("Email", p.Email)
            )
        )
    )
);
Класс Person выглядит так (для полноты картины):
public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

Теперь у вас есть простое, наглядное и контролируемое преобразование коллекции C# в XML!

6. Полезные методы и приёмы XElement и XDocument

Навигация

  • .Elements("TagName") — все непосредственные элементы (прямые потомки).
  • .Descendants("TagName") — все вложенные элементы где бы то ни было внутри текущего элемента.
  • .Parent — родительский элемент.
  • .Ancestors() — все предки элемента (полезно при поиске пути).
  • .FirstNode, .LastNode, .NextNode, .PreviousNode — навигация по соседям.

Поиск с помощью условий

var personsWithEmail = doc.Root
    .Elements("Person")
    .Where(p => !string.IsNullOrEmpty((string)p.Element("Email")));

Добавление и удаление

// Добавим телефон к уже существующему пользователю
var maria = doc.Root.Elements("Person")
    .FirstOrDefault(p => (string)p.Element("Name") == "Мария Петрова");

maria?.Element("Phones")?.Add(
    new XElement("Phone", new XAttribute("type", "work"), "+12 812 111-22-33")
);

// Удалить пользователя по id
doc.Root.Elements("Person")
    .Where(p => (string)p.Attribute("id") == "3")
    .Remove();

Преобразование обратно в строку/файл

string xmlText = doc.ToString(); // с отступами
doc.Save("contacts.xml");

7. Полезные нюансы

Сравнение LINQ to XML с другими подходами (.NET)

Подход Когда использовать Преимущества Недостатки
XmlDocument
Устаревший, старый код Интеграция с XPath Много кода, сложная API
XmlSerializer
Для сериализации объектов Просто сериализовать C# ↔ XML Гибкость низкая
LINQ to XML
Всё остальное: разбор, правка Линейный, лаконичный, мощный, LINQ Чуть больше памяти

Применение в реальных проектах и на собеседованиях

  • Разбора файлов настроек, где нет чёткого соответствия C#-классам.
  • Импорта/экспорта данных (например, выгрузка/загрузка пользовательских данных).
  • Миграций, обработки "грязных" или сложных XML-структур.
  • Быстрых преобразований (например, преобразование одного формата XML в другой).

"Зачем мне это знать?" — на собеседовании вас могут попросить преобразовать или распарсить какой-нибудь сложный XML, а помня LINQ to XML, вы сделаете это проще и красивее, чем с помощью старых API.

8. Типичные ошибки при работе с LINQ to XML

Ошибка №1: попытка обратиться к отсутствующему элементу без проверки.
Если элемент может отсутствовать, используйте оператор ?. или предварительную проверку на null. В противном случае получите NullReferenceException и неожиданный сбой прямо во время выполнения.

Ошибка №2: путаница между атрибутами и элементами.
Атрибуты и элементы — это разные сущности в XML. Иногда данные могут быть распределены между ними. Например:

<User login="admin"><Status>active</Status></User>

Чтобы получить значение атрибута login, используйте .Attribute("login"), а для получения содержимого тега <Status>.Element("Status").

Ошибка №3: недопонимание поведения метода .Remove().
Метод .Remove() сразу изменяет структуру XML-документа в памяти. Он не возвращает ничего, и "отменить" удаление уже нельзя. Новички иногда ожидают, что он вернёт удалённые элементы или как-то сигнализирует об изменении — это не так.

2
Задача
C# SELF, 48 уровень, 3 лекция
Недоступна
Извлечение данных из XML-документа
Извлечение данных из XML-документа
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ