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)
| Підхід | Коли використовувати | Переваги | Недоліки |
|---|---|---|---|
|
Старий (застарілий) код | Інтеграція з XPath | Багато коду, складний API |
|
Для серіалізації об’єктів | Проста серіалізація C# ↔ 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-документа в пам’яті. Він нічого не повертає, і «скасувати» видалення вже не можна. Початківці іноді очікують, що він поверне видалені елементи або хоч якось повідомить про зміну — це не так.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ