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 ?? "Без email";
    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-документа в пам’яті. Він нічого не повертає, і «скасувати» видалення вже не можна. Початківці іноді очікують, що він поверне видалені елементи або хоч якось повідомить про зміну — це не так.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ