Odczyt XML w C# najczęściej sprowadza się do wyboru właściwego narzędzia do konkretnego zadania. W praktyce temat c# read xml zwykle oznacza decyzję między prostym wczytaniem dokumentu do pamięci, strumieniowym przetwarzaniem dużego pliku albo deserializacją do klas, z którymi wygodniej pracować w aplikacji. Poniżej pokazuję, jak robię to najczęściej, na co zwracam uwagę i gdzie początkujący zwykle tracą czas.
Najpierw dobierz metodę, potem dopiero pisz kod
- XDocument wybieram, gdy XML jest mały lub średni i chcę wygodnie korzystać z LINQ.
- XmlReader sprawdza się przy dużych plikach, bo czyta dane sekwencyjnie i nie ładuje całego dokumentu do pamięci.
- XmlSerializer jest najlepszy, gdy XML ma stałą strukturę i chcesz zamienić go na obiekty C#.
- XmlDocument zostawiam głównie do starszego kodu albo wtedy, gdy potrzebuję klasycznego modelu DOM.
- Przy danych z zewnątrz trzymaj DTD wyłączone i nie zakładaj, że XML zawsze jest poprawny.
- Najwięcej problemów powodują namespace’y, błędne ścieżki do elementów i zbyt ciężkie podejście do małego zadania.
Najważniejsze decyzje przy odczycie XML
Zanim przejdziesz do kodu, warto odpowiedzieć sobie na jedno pytanie: czy chcesz czytać dokument jako drzewo, strumień czy obiekty. Od tej odpowiedzi zależy, czy użyjesz XDocument, XmlReader czy XmlSerializer. Ja zwykle nie zaczynam od składni, tylko od rozmiaru pliku, stabilności schematu i tego, czy XML ma być tylko odczytany, czy też później modyfikowany.
| Metoda | Kiedy używam | Plusy | Ograniczenia |
|---|---|---|---|
| XDocument | Małe i średnie pliki, szybkie zapytania LINQ | Wygodny kod, czytelne filtrowanie elementów | Ładuje cały dokument do pamięci |
| XmlReader | Duże pliki, logi, feedy, przetwarzanie sekwencyjne | Mały narzut pamięci, dobra wydajność | Trzeba pisać bardziej precyzyjny kod |
| XmlSerializer | Stała struktura XML i klasy domenowe | Najwygodniejsze mapowanie na obiekty | Wymaga dopasowania modelu do XML |
| XmlDocument | Legacy code, DOM, ręczne manipulacje węzłami | Pełna kontrola nad drzewem | Cięższe i mniej wygodne niż XDocument |
Jeśli dokument jest niewielki i chcesz pisać prosto, XDocument zwykle wygrywa. Gdy plik zaczyna rosnąć albo zależy Ci na pamięci, lepiej przejść do strumieniowego odczytu przez XmlReader. Ten wybór bardzo często decyduje o tym, czy kod będzie lekki i czysty, czy niepotrzebnie skomplikowany.
Jak wygodnie czytać małe pliki przez XDocument
XDocument traktuję jako mój domyślny wybór wtedy, gdy XML ma sensowną wielkość i chcę szybko dostać się do elementów po nazwie. To rozwiązanie dobrze pasuje do konfiguracji, prostych eksportów albo dokumentów, z których odczytuję kilka pól i kończę temat. W praktyce największą zaletą jest tu połączenie czytelności z LINQ.
using System;
using System.IO;
using System.Linq;
using System.Xml.Linq;
var doc = XDocument.Load("produkty.xml");
var products = doc.Root?
.Elements("product")
.Select(x => new
{
Id = (int?)x.Attribute("id"),
Name = (string?)x.Element("name"),
Price = (decimal?)x.Element("price")
})
.ToList();
if (products != null)
{
foreach (var product in products)
{
Console.WriteLine($"{product.Id}: {product.Name} - {product.Price}");
}
}Ten wariant jest prosty, ale ma jedną ważną cechę: cały dokument trafia do pamięci. Przy małym pliku to nie problem, przy dużym już tak. Druga rzecz, o której często się zapomina, to namespace’y. Jeśli XML ma przestrzeń nazw, zwykłe Element("name") przestaje działać i trzeba użyć XNamespace.
Ja lubię ten model wtedy, gdy kod ma być zrozumiały także za kilka miesięcy. Jeśli jednak plik jest duży albo dane przychodzą z zewnętrznego źródła, wolę przejść na bardziej ostrożny sposób czytania.
Gdy plik robi się duży, lepszy jest XmlReader
XmlReader działa inaczej niż XDocument: czyta dokument sekwencyjnie, bez budowania całego drzewa w pamięci. To jest dokładnie ten scenariusz, w którym zwykle wygrywa wydajność. Jeśli przetwarzasz duży eksport, logi lub strumień danych, ta różnica szybko staje się odczuwalna.
using System;
using System.Xml;
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit
};
using var reader = XmlReader.Create("produkty.xml", settings);
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "product")
{
var id = reader.GetAttribute("id");
Console.WriteLine($"Produkt o id: {id}");
}
}Ten kod pokazuje najważniejszą ideę: nie pytasz dokumentu o całość, tylko reagujesz na kolejne węzły. To mniej wygodne niż LINQ do XML, ale za to bardzo rozsądne przy większych plikach. W oficjalnej dokumentacji Microsoftu XmlReader jest opisany właśnie jako czytnik działający sekwencyjnie, co dobrze oddaje jego charakter.
W praktyce używam też ustawień bezpieczeństwa. Gdy XML pochodzi spoza systemu, nie chcę domyślnie przetwarzać DTD. W wielu projektach wystarczy DtdProcessing = Prohibit, a jeśli dokument ma pochodzić z pełni zaufanego źródła, dopiero wtedy rozważam bardziej liberalne ustawienia. To mały detal, który potrafi oszczędzić duży problem.
Jeśli jednak XML ma odwzorowywać dane niemal 1:1 na klasy w aplikacji, bardziej naturalnym krokiem jest deserializacja.
Kiedy warto mapować XML na klasy
XmlSerializer wybieram wtedy, gdy XML ma przewidywalną strukturę i chcę od razu pracować na obiektach domenowych. To dobre rozwiązanie dla konfiguracji, integracji z prostym API albo wymiany danych między systemami, gdzie format jest stały. Zamiast ręcznie szukać elementów, dostaję gotowy obiekt z właściwościami.
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
[XmlRoot("catalog")]
public class Catalog
{
[XmlElement("product")]
public List Products { get; set; } = new();
}
public class Product
{
[XmlAttribute("id")]
public int Id { get; set; }
[XmlElement("name")]
public string Name { get; set; } = string.Empty;
[XmlElement("price")]
public decimal Price { get; set; }
}
var serializer = new XmlSerializer(typeof(Catalog));
using var stream = File.OpenRead("produkty.xml");
var catalog = (Catalog?)serializer.Deserialize(stream);
if (catalog != null)
{
foreach (var product in catalog.Products)
{
Console.WriteLine($"{product.Id}: {product.Name} - {product.Price}");
}
} Tu liczy się zgodność modelu z XML. Jeśli nazwa głównego węzła albo atrybutów nie pasuje do klas, trzeba użyć atrybutów typu [XmlRoot], [XmlElement] i [XmlAttribute]. Warto też pamiętać, że serializer najlepiej działa z publicznymi właściwościami i klasą, którą da się sensownie utworzyć bez skomplikowanego konstruktora. To nie jest narzędzie do każdego XML-a, ale do stabilnych struktur sprawdza się bardzo dobrze.
Gdy ten model działa, kod biznesowy robi się zauważalnie prostszy. Zamiast ciągle pytać o węzły, pracujesz już na obiektach, a to zwykle ułatwia testowanie i dalszy rozwój aplikacji.
Pułapki, które widzę najczęściej
Najwięcej błędów nie wynika z samego parsera, tylko z założeń. XML bywa poprawny składniowo, a mimo to nie daje się odczytać tak, jak oczekujesz. Poniżej zebrałem problemy, które wracają najczęściej.
| Problem | Jak się objawia | Co robię |
|---|---|---|
| Błędna składnia XML |
XmlException albo brak możliwości wczytania pliku |
Sprawdzam domknięcie tagów, encje i poprawność struktury |
| Namespace’y | Elementy zwracają null mimo że istnieją |
Używam XNamespace albo odpowiednio konfiguruję nazwy w serializacji |
| Zbyt duży plik | Wysokie zużycie pamięci, wolne działanie | Przechodzę z XDocument na XmlReader |
| Niepasujący model klas | Błędy przy deserializacji | Dodaję [XmlRoot], [XmlElement] i sprawdzam nazwy węzłów |
| Nieznane źródło XML | Ryzyko związane z DTD i zewnętrznymi encjami | Wyłączam DTD w XmlReaderSettings
|
| Złe kodowanie | Znaki narodowe są uszkodzone | Wczytuję plik z właściwym encodingiem i nie mieszam encji z tekstem |
Jeśli miałbym wskazać jeden błąd, który widać najczęściej u początkujących, to byłoby zakładanie, że XML zawsze jest „po prostu tekstem”. To format ustrukturyzowany, więc kolejność elementów, namespace’y i atrybuty naprawdę mają znaczenie. Kiedy to zaakceptujesz, połowa problemów znika jeszcze zanim powstanie pierwszy wyjątek.
Mój praktyczny skrót wyboru do codziennej pracy
Gdy pracuję nad zwykłą funkcją w aplikacji webowej, wybór robię bardzo szybko. Mały plik konfiguracyjny albo eksport do ręcznego przeglądania czytam przez XDocument. Duży dokument, feed albo integrację, gdzie liczy się pamięć, obsługuję przez XmlReader. Jeżeli XML ma odwzorować konkretne dane biznesowe, a struktura jest stabilna, biorę XmlSerializer.
XmlDocument zostawiam raczej wtedy, gdy mam do czynienia z istniejącym projektem albo muszę ręcznie przesuwać węzły po drzewie. Sam w sobie nie jest zły, ale w nowych projektach rzadko daje mi przewagę nad XDocument. W codziennej pracy bardziej liczy się to, jak szybko mogę doprowadzić kod do czytelnego i bezpiecznego stanu, niż sama liczba dostępnych metod.
Jeśli mam zostawić jedną zasadę, brzmi ona tak: najpierw oceń rozmiar XML i sposób pracy z danymi, a dopiero potem wybierz klasę. To prosty filtr, który porządkuje cały temat i sprawia, że odczyt XML w C# przestaje być chaotycznym zgadywaniem, a staje się zwykłą decyzją techniczną.