1. Wprowadzenie
XML (eXtensible Markup Language) — rozszerzalny język znaczników, popularny format do przechowywania i wymiany złożonych, strukturalnych danych. W przeciwieństwie do JSON XML nie jest tak zwięzły, ale nadal jest szeroko używany w bankowości, wymianie między serwisami państwowymi, dokumentach i integracjach z Microsoft Dynamics 365, SAP, Oracle etc Niektóre dane w Windows i .NET (np. pliki konfiguracyjne) dalej bywają przechowywane właśnie w formacie XML.
W formie XML przypomina HTML, tylko bez stałego zestawu tagów typu <body> czy <div> — sam definiujesz strukturę elementów. Przykład:
<Person>
<FirstName>Iwan</FirstName>
<LastName>Pietrow</LastName>
<Age>30</Age>
</Person>
Podstawy XML i dopasowanie do obiektów C#
W odróżnieniu od JSON, w XML nie ma specjalnych typów dla tablic czy wartości boolowskich — wszystko reprezentowane jest elementami i atrybutami. Struktury-listy realizowane są jako powtarzające się elementy o tej samej nazwie.
Aby wygodnie pracować z XML w C#, używa się mechanizmu serializacji/deserializacji przez XmlSerializer.
Serializacja — przekształcenie obiektu C# do ciągu XML. Deserializacja — odwrotny proces: odczytanie obiektu C# z XML.
To działa "od ręki", jeśli twoje klasy spełniają pewne wymagania — niżej je omówimy.
Po co używać XmlSerializer?
XmlSerializer — standardowe narzędzie .NET (przestrzeń nazw System.Xml.Serialization) do konwersji obiektów na XML i z powrotem. Jest proste, typowo-bezpieczne i nadaje się do importu/eksportu danych, konfigów, wymiany między systemami i integracji z serwisami państwowymi.
2. Przykład: serializacja obiektu
Mamy model użytkownika:
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
Serializujemy obiekt do XML:
using System.Xml.Serialization;
User user = new User { FirstName = "Iwan", LastName = "Pietrow", Age = 30 };
// Do serializacji potrzebny jest typ
XmlSerializer serializer = new XmlSerializer(typeof(User));
// Zapisujemy XML do pliku
using FileStream fs = new FileStream("user.xml", FileMode.Create);
serializer.Serialize(fs, user);
Co się dzieje?
- Tworzymy obiekt klasy User.
- Tworzymy egzemplarz XmlSerializer dla typu User.
- Otwieramy plik przez FileStream.
- Wywołujemy metodę Serialize(), która zamienia obiekt na XML i zapisuje go do pliku.
Zawartość pliku 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>Iwan</FirstName>
<LastName>Pietrow</LastName>
<Age>30</Age>
</User>
Tak prosto — minimum kodu i gotowy XML.
3. Deserializacja: czytamy XML z powrotem do obiektu
Czytamy stworzony XML i odtwarzamy obiekt C#:
// Otwieramy plik XML
using FileStream fs = new FileStream("user.xml", FileMode.Open);
User? loadedUser = serializer.Deserialize(fs) as User;
Console.WriteLine($"{loadedUser?.FirstName} {loadedUser?.LastName}, wiek: {loadedUser?.Age}");
Metoda Deserialize() zwraca object, więc robimy rzutowanie na User (przez as). Jeśli plik jest poprawny i odpowiada modelowi — obiekt zostanie w pełni odtworzony.
4. Jak działa serializacja: typowe cechy
Serializacja do XML wymaga przestrzegania zasad:
• Klasa do serializacji powinna mieć publiczny konstruktor domyślny (public bez parametrów).
• Serializowane właściwości — publiczne: potrzebny jest zarówno getter, jak i setter. Prywatne pola są ignorowane.
• Nie działają właściwości tylko do odczytu, a także właściwości statyczne i abstrakcyjne.
Przy złamaniu zasad otrzymasz InvalidOperationException z komunikatem typu "XmlSerializer cannot serialize this type".
5. Serializacja kolekcji: tablice i listy
Jeśli trzeba zserializować kilku użytkowników — używamy klasy opakowującej:
public class UserList
{
public List<User> Users { get; set; } = new List<User>();
}
Utwórzmy kolekcję i zserializujmy:
UserList users = new UserList
{
Users = new List<User>
{
new User { FirstName = "Iwan", LastName = "Pietrow", Age = 30 },
new User { FirstName = "Maria", LastName = "Iwanowa", Age = 25 }
}
};
XmlSerializer serializer = new XmlSerializer(typeof(UserList));
using FileStream fs = new FileStream("users.xml", FileMode.Create);
serializer.Serialize(fs, users);
Otrzymamy 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>Iwan</FirstName>
<LastName>Pietrow</LastName>
<Age>30</Age>
</User>
<User>
<FirstName>Maria</FirstName>
<LastName>Iwanowa</LastName>
<Age>25</Age>
</User>
</Users>
</UserList>
Czy można zserializować po prostu List<User>? Można, ale element root będzie nazywał się ArrayOfUser, co nie zawsze pasuje do integracji.
6. Atrybuty do sterowania serializacją
Przez atrybuty możesz sterować formatem XML: zmieniać nazwy elementów, serializować jako atrybuty, ignorować pola etc Przydatne atrybuty: [XmlElement], [XmlAttribute], [XmlArray], [XmlIgnore].
Serializacja jako atrybuty
Zapiszemy imię i nazwisko jako atrybuty, a wiek jako element:
public class User
{
[XmlAttribute]
public string FirstName { get; set; }
[XmlAttribute]
public string LastName { get; set; }
[XmlElement]
public int Age { get; set; }
}
Wynik:
<User FirstName="Iwan" LastName="Pietrow">
<Age>30</Age>
</User>
Zarządzanie nazwami elementów
Zmodyfikujemy tagi na bardziej czytelne dla człowieka:
public class User
{
[XmlElement("Imię")]
public string FirstName { get; set; }
[XmlElement("Nazwisko")]
public string LastName { get; set; }
[XmlElement("Wiek")]
public int Age { get; set; }
}
Resultujący XML:
<User>
<Imię>Iwan</Imię>
<Nazwisko>Pietrow</Nazwisko>
<Wiek>30</Wiek>
</User>
Ignorowanie właściwości
Jeśli właściwości nie trzeba serializować — oznacz ją [XmlIgnore]:
public class User
{
public string FirstName { get; set; }
[XmlIgnore]
public string InternalCode { get; set; }
}
7. Serializacja hierarchii i obiektów zagnieżdżonych
Zagnieżdżone klasy łatwo stają się zagnieżdżonymi elementami 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 = "Anna",
Address = new Address { City = "NeonCity", Street = "Elm" }
};
XmlSerializer serializer = new XmlSerializer(typeof(User));
using var fs = new FileStream("user_with_address.xml", FileMode.Create);
serializer.Serialize(fs, user);
XML:
<User>
<FirstName>Anna</FirstName>
<Address>
<City>NeonCity</City>
<Street>Elm</Street>
</Address>
</User>
8. Serializacja do/z ciągu (bez plików)
Serializacja do ciągu
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);
Deserializacja z ciągu
var xml = "<User><FirstName>Anna</FirstName></User>";
using var stringReader = new StringReader(xml);
User user = (User)serializer.Deserialize(stringReader);
Console.WriteLine(user.FirstName);
9. Przykład: eksport i import danych aplikacji
Eksport/import listy użytkowników do pliku 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);
}
Zastosowanie: kopia zapasowa, wymiana z innymi serwisami, integracje. Na rozmowach rekrutacyjnych pytania o XmlSerializer pojawiają się częściej, niż się wydaje — szczególnie w firmach pracujących z serwisami państwowymi i bankami.
10. Typowe błędy i cechy działania
Jeśli pole nie jest public, nie zostanie zserializowane.
Właściwości tylko do odczytu są ignorowane.
Właściwości o typie interfejsu lub abstrakcyjnej klasy nie są wspierane przez serializator.
Typ DateTime jest serializowany w formacie XSD (patrz dokumentację).
Kolekcje typu tablice i List<T> działają przewidywalnie, a z ObservableCollection<T> mogą być niespodzianki u systemów zewnętrznych.
Niuanse kodowań: przy zapisie przez StreamWriter bez jawnego ustawienia kodowania wynik może być w UTF-16; dla kompatybilności częściej oczekuje się UTF-8 — ustawiajcie kodowanie jawnie.
GO TO FULL VERSION