1. Вступ
XML (eXtensible Markup Language) — розширювана мова розмітки, популярний формат для зберігання й обміну складними структурованими даними. На відміну від JSON, XML не відзначається лаконічністю, проте досі активно використовується в банківській сфері, обміні між державними сервісами, документах та інтеграціях з 1С, SAP, Oracle тощо. Деякі дані у Windows та .NET (наприклад, файли конфігурації) досі зберігаються саме у форматі XML.
Візуально XML схожий на HTML, але без фіксованого набору тегів на кшталт <body> і <div> — ви самі визначаєте структуру своїх елементів. Приклад:
<Person>
<FirstName>Ivan</FirstName>
<LastName>Petrov</LastName>
<Age>30</Age>
</Person>
Основи XML і відповідність об’єктам C#
На відміну від JSON, у XML немає спеціальних типів для масивів чи булевих значень — усе подано елементами та атрибутами. Структури-списки реалізують як повторювані елементи з однаковою назвою.
Щоб зручно працювати з XML у C#, використовують механізм серіалізації та десеріалізації через XmlSerializer.
Серіалізація — перетворення C#-об’єкта на XML-рядок. Десеріалізація — зворотний процес: отримання C#-об’єкта з XML.
Це працює без додаткових налаштувань, якщо ваші класи відповідають низці вимог — нижче розглянемо.
Навіщо використовувати XmlSerializer?
XmlSerializer — стандартний інструмент .NET (простір імен System.Xml.Serialization) для перетворення об’єктів у XML і назад. Він простий, типобезпечний і підходить для імпорту й експорту даних, конфігураційних файлів, обміну між системами та інтеграцій із державними сервісами.
2. Приклад: серіалізація об’єкта
Припустімо, що маємо модель користувача:
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
Серіалізуємо об’єкт у XML:
using System.Xml.Serialization;
User user = new User { FirstName = "Іван", LastName = "Петров", Age = 30 };
// Для серіалізації потрібен тип
XmlSerializer serializer = new XmlSerializer(typeof(User));
// Записуємо XML у файл
using FileStream fs = new FileStream("user.xml", FileMode.Create);
serializer.Serialize(fs, user);
Що відбувається?
- Створюємо об’єкт класу User.
- Створюємо екземпляр XmlSerializer для типу User.
- Відкриваємо файл через FileStream.
- Викликаємо метод Serialize(), який перетворює об’єкт у XML і записує його у файл.
Вміст файлу user.xml:
<?xml version="1.0"?>
<User xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FirstName>Іван</FirstName>
<LastName>Петров</LastName>
<Age>30</Age>
</User>
Ось і все — мінімум коду й готовий XML.
3. Десеріалізація: читаємо XML назад у об’єкт
Читаємо створений XML і відновлюємо об’єкт C#:
// Відкриваємо XML-файл
using FileStream fs = new FileStream("user.xml", FileMode.Open);
User? loadedUser = serializer.Deserialize(fs) as User;
Console.WriteLine($"{loadedUser?.FirstName} {loadedUser?.LastName}, вік: {loadedUser?.Age}");
Метод Deserialize() повертає object, тому виконуємо приведення до User (через as). Якщо файл коректний і відповідає моделі, об’єкт відновиться повністю.
4. Як працює серіалізація: типові особливості
Серіалізація в XML вимагає дотримання правил:
• Клас для серіалізації має мати публічний конструктор за замовчуванням (public без параметрів).
• Властивості, що серіалізуються, — публічні: потрібні обидва аксесори: get і set. Приватні члени ігноруються.
• Властивості лише для читання, а також статичні й абстрактні властивості не підтримуються.
Якщо порушити правила, отримаєте InvalidOperationException із повідомленням на кшталт «XmlSerializer cannot serialize this type».
5. Серіалізація колекцій: масиви й списки
Якщо потрібно серіалізувати кількох користувачів — використайте клас-обгортку:
public class UserList
{
public List<User> Users { get; set; } = new List<User>();
}
Створимо колекцію й серіалізуємо:
UserList users = new UserList
{
Users = new List<User>
{
new User { FirstName = "Іван", LastName = "Петров", Age = 30 },
new User { FirstName = "Марія", LastName = "Іванова", Age = 25 }
}
};
XmlSerializer serializer = new XmlSerializer(typeof(UserList));
using FileStream fs = new FileStream("users.xml", FileMode.Create);
serializer.Serialize(fs, users);
Отримаємо XML:
<?xml version="1.0"?>
<UserList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Users>
<User>
<FirstName>Іван</FirstName>
<LastName>Петров</LastName>
<Age>30</Age>
</User>
<User>
<FirstName>Марія</FirstName>
<LastName>Іванова</LastName>
<Age>25</Age>
</User>
<Users>
</UserList>
Чи можна серіалізувати просто List<User>? Можна, але кореневий елемент називатиметься ArrayOfUser, що не завжди підходить для інтеграцій.
6. Атрибути для керування серіалізацією
Через атрибути можна керувати форматом XML: перейменовувати елементи, серіалізувати як атрибути, ігнорувати поля тощо. Корисні атрибути: [XmlElement], [XmlAttribute], [XmlArray], [XmlIgnore].
Серіалізація у вигляді атрибутів
Запишемо імʼя й прізвище як атрибути, а вік — як елемент:
public class User
{
[XmlAttribute]
public string FirstName { get; set; }
[XmlAttribute]
public string LastName { get; set; }
[XmlElement]
public int Age { get; set; }
}
Результат:
<User FirstName="Іван" LastName="Петров">
<Age>30</Age>
</User>
Керування іменами елементів
Перейменуємо теги на зрозуміліші:
public class User
{
[XmlElement("Ім'я")]
public string FirstName { get; set; }
[XmlElement("Прізвище")]
public string LastName { get; set; }
[XmlElement("Вік")]
public int Age { get; set; }
}
Підсумковий XML:
<User>
<Ім'я>Іван</Ім'я>
<Прізвище>Петров</Прізвище>
<Вік>30</Вік>
</User>
Ігнорування властивостей
Якщо властивість не потрібно серіалізувати, позначте її [XmlIgnore]:
public class User
{
public string FirstName { get; set; }
[XmlIgnore]
public string InternalCode { get; set; }
}
7. Серіалізація ієрархії та вкладених об’єктів
Вкладені класи легко перетворюються на вкладені XML-елементи:
public class Address
{
public string City { get; set; }
public string Street { get; set; }
}
public class User
{
public string FirstName { get; set; }
public Address Address { get; set; }
}
var user = new User
{
FirstName = "Анна",
Address = new Address { City = "Неонград", Street = "В'язова" }
};
XmlSerializer serializer = new XmlSerializer(typeof(User));
using var fs = new FileStream("user_with_address.xml", FileMode.Create);
serializer.Serialize(fs, user);
XML:
<User>
<FirstName>Анна</FirstName>
<Address>
<City>Неонград</City>
<Street>В'язова</Street>
</Address>
</User>
8. Серіалізація у/з рядка (без файлів)
Серіалізація у рядок
using System.Text;
var serializer = new XmlSerializer(typeof(User));
using var stringWriter = new StringWriter();
serializer.Serialize(stringWriter, user);
string xml = stringWriter.ToString();
Console.WriteLine(xml);
Десеріалізація з рядка
var xml = "<User><FirstName>Анна</FirstName></User>";
using var stringReader = new StringReader(xml);
User user = (User)serializer.Deserialize(stringReader);
Console.WriteLine(user.FirstName);
9. Приклад: експорт і імпорт даних застосунку
Експорт і імпорт списку користувачів у файл XML:
public static void ExportUsers(UserList users, string path)
{
var serializer = new XmlSerializer(typeof(UserList));
using var fs = new FileStream(path, FileMode.Create);
serializer.Serialize(fs, users);
}
public static UserList ImportUsers(string path)
{
var serializer = new XmlSerializer(typeof(UserList));
using var fs = new FileStream(path, FileMode.Open);
return (UserList)serializer.Deserialize(fs);
}
Застосування: резервне копіювання, обмін з іншими сервісами, інтеграції. На співбесідах питання про XmlSerializer трапляються частіше, ніж здається — особливо в компаніях, що працюють із державними сервісами та банками.
10. Типові помилки та особливості роботи
Якщо поле — не public, воно не серіалізується.
Властивості лише для читання ігноруються.
Властивості з типом інтерфейсу або абстрактного класу серіалізатор не підтримує.
Тип DateTime серіалізується у форматі XSD (див. документацію).
Колекції типу масивів і List<T> працюють передбачувано, а з ObservableCollection<T> у сторонніх систем можуть бути сюрпризи.
Особливості кодувань: під час виведення через StreamWriter без явного зазначення кодування результат може вийти у UTF-16; для сумісності зазвичай очікують UTF-8 — зазначайте кодування явно.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ