1. Вступ
Ви вже знаєте, що .NET уміє серіалізувати й десеріалізувати об’єкти в XML за замовчуванням, спираючись на публічні поля та властивості. Та коли потрібно отримати акуратний XML, сумісний із зовнішніми програмами й стандартами, або коли не слід додавати до XML певні внутрішні деталі, знадобиться тонке налаштування.
У C# для цього призначені атрибути серіалізації — спеціальні «мітки», якими позначають класи, властивості або поля. Завдяки їм можна керувати практично всім: які поля потраплять до XML, які ігнорувати, як саме називатиметься елемент, чи перетворювати його на атрибут, чи надавати йому складну структуру, а також визначати порядок елементів.
Уявімо серіалізацію за замовчуванням як автозаповнення анкети: імʼя, прізвище, по батькові — у свої клітинки, решта — як вийде. А з атрибутами ви самі вирішуєте, який рядок куди записати, що пропустити, що виділити, а що взагалі сховати.
Відповідність об’єкта та XML
| Клас/Властивість | Атрибут | Результат у XML |
|---|---|---|
|
Клас UserList | |
|
|
|
|
|
id="..." (атрибут) |
|
|
|
|
|
|
|
[XmlIgnore] (див. RegisteredAtString) | - |
|
|
|
|
|
|
2. Огляд основних атрибутів XML-серіалізації
Пройдімося найпоширенішими атрибутами, які .NET‑розробники використовують уже багато років!
Атрибут [XmlElement] — ім’я елемента
Змінює ім’я елемента в XML або позначає властивість/поле як XML‑елемент.
public class Person
{
[XmlElement("FullName")]
public string Name { get; set; }
}
XML:
<Person>
<FullName>Василь Петров</FullName>
</Person>
Якщо атрибут не вказати — за замовчуванням ім’я елемента збігатиметься з ім’ям властивості/поля.
Атрибут [XmlAttribute] — серіалізація у XML-атрибут
Дозволяє серіалізувати властивість/поле як атрибут XML, а не як елемент.
public class Person
{
[XmlAttribute("id")]
public int Id { get; set; }
}
XML:
<Person id="123"></Person>
У реальних XML‑API атрибути часто використовують для ідентифікаторів, дат, прапорців тощо.
Атрибут [XmlIgnore] — пропустити поле/властивість
Цей атрибут повністю виключає властивість із процесу серіалізації та десеріалізації: позначене ним поле або властивість просто не потрапить до фінального XML.
public class Person
{
[XmlIgnore]
public string InternalNote { get; set; }
}
XML: (властивості InternalNote тут немає і не буде)
Атрибути [XmlArray] і [XmlArrayItem] — контроль над колекціями
Спеціальні атрибути для масивів і колекцій. [XmlArray] задає ім’я зовнішньої обгортки масиву, а [XmlArrayItem] — ім’я окремого елемента.
public class Person
{
[XmlArray("Phones")]
[XmlArrayItem("Phone")]
public string[] PhoneNumbers { get; set; }
}
XML:
<Person>
<Phones>
<Phone>+12951234567</Phone>
<Phone>+12876543210</Phone>
</Phones>
</Person>
Атрибут [XmlRoot] — назва кореневого елемента
Позначає сам клас, щоб змінити ім’я кореневого елемента в XML.
[XmlRoot("User")]
public class Person
{
public string Name { get; set; }
}
XML:
<User>
<Name>Анна</Name>
</User>
Атрибут [XmlText] — серіалізація у текстовий вміст елемента
Іноді потрібно, щоб значення властивості стало просто текстом усередині елемента, а не його вкладеним піделементом або атрибутом.
public class Note
{
[XmlText]
public string Content { get; set; }
}
XML:
<Note>Зателефонувати бабусі!</Note>
Атрибути [XmlNamespaceDeclarations], [XmlElement(Type = ...)]
Для складніших сценаріїв (наприклад, робота з просторами імен, підтримка наслідування тощо) існують і специфічніші атрибути. Радимо переглянути офіційну документацію Microsoft для повного переліку.
3. Практика: удосконалюємо серіалізацію на реальному прикладі
Пройдімо крок за кроком і удосконалимо наш навчальний застосунок, довівши XML‑серіалізацію до високого рівня — щоб вона відповідала навіть найприскіпливішим вимогам.
Базовий клас без атрибутів
Припустімо, у нас є такий клас користувача:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string[] Emails { get; set; }
public DateTime RegisteredAt { get; set; }
public string Password { get; set; }
}
Серіалізація за замовчуванням сформує приблизно такий XML (і ще й дату збереже у власному форматі):
<User>
<Id>42</Id>
<Name>Ivan Ivanov</Name>
<Emails>
<string>user@mail.com</string>
<string>admin@site.org</string>
</Emails>
<RegisteredAt>2024-07-01T00:00:00</RegisteredAt>
<Password>qwerty</Password>
</User>
Що не так:
- Пароль краще взагалі не серіалізувати.
- Хочемо мати дату як атрибут і в зрозумілому форматі.
- Хочемо список електронних адрес у зручному вигляді.
- Хочемо кореневий елемент <Person>.
Додаємо налаштування через атрибути
[XmlRoot("Person")]
public class User
{
[XmlAttribute("id")]
public int Id { get; set; }
[XmlElement("FullName")]
public string Name { get; set; }
[XmlArray("EMails")]
[XmlArrayItem("Email")]
public string[] Emails { get; set; }
[XmlAttribute("registered")]
public string RegisteredAtString
{
get => RegisteredAt.ToString("yyyy-MM-dd");
set => RegisteredAt = DateTime.Parse(value);
}
[XmlIgnore]
public string Password { get; set; }
[XmlIgnore]
public DateTime RegisteredAt { get; set; }
}
Пояснення тонкощів:
- Використовуємо [XmlAttribute("registered")] для серіалізації дати як атрибута.
- Щоб серіалізувати не сам DateTime, а його рядкове представлення, додаємо окрему властивість RegisteredAtString, а реальне поле приховуємо через [XmlIgnore].
- Пароль приховуємо за допомогою [XmlIgnore].
- Масив електронних адрес налаштовано так, що зовнішній тег — <EMails>, а кожна адреса — <Email>.
Результат:
<Person id="42" registered="2024-07-01">
<FullName>Ivan Ivanov</FullName>
<EMails>
<Email>user@mail.com</Email>
<Email>admin@site.org</Email>
</EMails>
</Person>
4. Робота з вкладеними об’єктами
XML особливо зручний, коли є вкладені об’єкти (наприклад, користувач і його адреси).
public class Address
{
[XmlAttribute]
public string City { get; set; }
[XmlText]
public string Details { get; set; }
}
public class User
{
// ...інші властивості, див. вище...
public Address[] Addresses { get; set; }
}
Додамо налаштування для масиву адрес:
[XmlArray("UserAddresses")]
[XmlArrayItem("Address")]
public Address[] Addresses { get; set; }
Тоді XML виглядатиме так:
<Person id="42" registered="2024-07-01">
<FullName>Ivan Ivanov</FullName>
<EMails>
<Email>user@mail.com</Email>
</EMails>
<UserAddresses>
<Address City="Neon city">вул. Замкова, буд. 1</Address>
<Address City="North Cave">вул. Крайня буд. 12</Address>
</UserAddresses>
</Person>
Видно, що в кожному <Address> атрибут City містить назву міста, а саму адресу вказано як текст усередині елемента.
5. Поширені помилки та нюанси
Якщо ви серіалізуєте складний об’єкт і не бачите у XML очікуваних змін — найімовірніше, забули правильно вказати атрибут. Іноді буває, що властивості/поля залишаються без маркування. Тоді .NET просто обирає поведінку за замовчуванням.
Ще одна пастка — ім’я колекції. Якщо колекція (наприклад, масив рядків) не позначена ні [XmlArray], ні [XmlArrayItem], то елементи запишуться з тегом string (як видно в початковому прикладі вище). Щоб цього не було, завжди явно задавайте імена тегів через атрибути.
Будьте обережні з властивостями типу «getter‑only» (лише для читання) — такі властивості за замовчуванням не десеріалізуються. Намагайтеся залишати публічний set.
Деякі типи (наприклад, словники або інтерфейси) стандартний XmlSerializer не серіалізує або вони потребують окремої обробки.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ