1. Introduction
L'arrivée de LINQ a été une révolution pour travailler avec le XML en .NET. Avant, pour modifier du XML il fallait écrire des constructions lourdes avec NodeList et XmlElement, maintenant on peut exprimer toutes les opérations dans le style LINQ. C'est comme passer sur une autoroute rapide après s'être perdu longtemps sur des chemins de gravier.
LINQ to XML résout trois tâches principales :
- Simplifie la syntaxe de création et de lecture du XML.
- Fait ressembler la navigation dans le document au travail avec des collections.
- Permet d'intégrer le XML avec n'importe quelle requête LINQ.
Classes principales : XDocument, XElement, XAttribute
Avant de plonger dans les eaux profondes de LINQ to XML, rencontrons les trois piliers :
- XDocument — c'est la racine de tout le document, l'équivalent de la tranche d'un livre où tout est contenu.
- XElement — n'importe quel nœud (élément) XML. C'est comme une boîte dans un entrepôt qui peut contenir d'autres boîtes ou des marchandises (éléments, attributs et texte).
- XAttribute — un attribut d'un élément, par exemple, <User id="42" />.
Schéma visuel
+-----------------+
| XDocument |---------------------+
+-----------------+ |
| (Root XElement)
v |
+-------------+ +-----------------+
| XElement |<-------------| XElement (root) |
+-------------+ +-----------------+
| |
(XAttribute) (nested XElements)
2. Lecture du XML avec LINQ to XML
Imaginons que dans notre application d'apprentissage (par exemple, un carnet d'adresses) nous voulons lire des contacts depuis un fichier XML. Supposons que la structure du fichier soit la suivante :
<Contacts>
<Person id="1">
<Name>Ivan Ivanov</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>Maria Petrova</Name>
<Email>maria@example.com</Email>
<Phones>
<Phone type="mobile">+12 921 123-45-67</Phone>
</Phones>
</Person>
</Contacts>
Lire le XML en mémoire
Commençons par ajouter la directive au projet :
using System.Xml.Linq;
Maintenant, lisons le fichier ou la chaîne :
// Supposons que xmlString contient le document XML, ou on peut utiliser XDocument.Load("contacts.xml")
var doc = XDocument.Parse(xmlString);
// On obtient la liste des Person
var persons = doc.Root.Elements("Person");
foreach (var person in persons)
{
string name = person.Element("Name")?.Value ?? "Sans nom";
string email = person.Element("Email")?.Value ?? "Sans e-mail";
string id = person.Attribute("id")?.Value ?? "pas d'id";
Console.WriteLine($"Nom : {name}, Email : {email}, id : {id}");
}
- doc.Root donne l'élément racine (Contacts).
- Elements("Person") renvoie tous les <Person>.
- Ensuite on prend les éléments/attributs nécessaires via .Element("...") ou .Attribute("...").
- L'opérateur ?. protège contre les erreurs si l'élément/attribut n'est pas trouvé.
Analogie
Travailler avec XElement — c'est comme démonter une matriochka : tu sors une poupée, dedans il y en a d'autres, et ainsi de suite. Sauf que maintenant c'est fait avec une requête simple, et pas avec des parcours lourds.
3. Écrire des requêtes LINQ sur le XML
Vous voulez obtenir la liste de tous les téléphones mobiles de toutes les personnes ? Facile, en style LINQ :
var mobilePhones = doc
.Descendants("Phone") // on cherche tous les Phone à n'importe quel niveau
.Where(p => (string)p.Attribute("type") == "mobile")
.Select(p => p.Value);
foreach (string phone in mobilePhones)
{
Console.WriteLine($"Mobile : {phone}");
}
- Descendants("Phone") cherche tous les tags <Phone> dans le document.
- Attribute("type") — on obtient l'attribut.
- Le cast (string) sur l'attribut renvoie automatiquement une chaîne ou null.
Astuces sympas
On peut chercher sur plusieurs niveaux sans se soucier d'une imbrication complexe. Ce n'est pas simple à faire avec un XmlDocument classique sans s'embêter.
4. Création et modification du XML "à la volée"
Il est pratique de créer du XML directement en mémoire avec les constructeurs XElement et XDocument. Voici un exemple pour ajouter une nouvelle personne dans notre document (rappel : c'est pour notre "application-annuaire" en évolution) :
// On crée un nouveau Person
var newPerson = new XElement("Person",
new XAttribute("id", "3"),
new XElement("Name", "Sergey Novikov"),
new XElement("Email", "sergey@example.com"),
new XElement("Phones",
new XElement("Phone", new XAttribute("type", "work"), "+12 812 987-65-43")
)
);
// On ajoute à la racine
doc.Root.Add(newPerson);
// On sauvegarde à nouveau dans le fichier (ou en chaîne)
doc.Save("contacts.xml");
Commentaires :
- On peut imbriquer des éléments avec des constructeurs "matriochka" : le premier argument est le tag, les autres sont le contenu imbriqué (peuvent être des éléments, attributs, texte).
- Les attributs viennent toujours avant le contenu direct (c.-à-d. <Phone type="work">...).
5. Interaction avec les collections C# — la magie de LINQ
On a souvent besoin de sérialiser une collection d'objets (par exemple, List<Person>) en XML ou inversement. C'est très simple, presque en une seule ligne LINQ :
var people = new List<Person>
{
new Person { Id = 1, Name = "Ivan Ivanov", Email = "ivan@example.com" },
// etc.
};
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)
)
)
)
);
La classe Person ressemble à ceci (pour compléter) :
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
Maintenant vous avez une transformation simple, claire et contrôlée d'une collection C# en XML !
6. Méthodes utiles et astuces pour XElement et XDocument
Navigation
- .Elements("TagName") — tous les éléments directs (enfants immédiats).
- .Descendants("TagName") — tous les éléments imbriqués où qu'ils se trouvent à l'intérieur de l'élément courant.
- .Parent — l'élément parent.
- .Ancestors() — tous les ancêtres de l'élément (utile pour retrouver un chemin).
- .FirstNode, .LastNode, .NextNode, .PreviousNode — navigation parmi les voisins.
Recherche avec conditions
var personsWithEmail = doc.Root
.Elements("Person")
.Where(p => !string.IsNullOrEmpty((string)p.Element("Email")));
Ajout et suppression
// On ajoute un téléphone à un utilisateur existant
var maria = doc.Root.Elements("Person")
.FirstOrDefault(p => (string)p.Element("Name") == "Maria Petrova");
maria?.Element("Phones")?.Add(
new XElement("Phone", new XAttribute("type", "work"), "+12 812 111-22-33")
);
// Supprimer un utilisateur par id
doc.Root.Elements("Person")
.Where(p => (string)p.Attribute("id") == "3")
.Remove();
Conversion de retour en chaîne/fichier
string xmlText = doc.ToString(); // avec indentation
doc.Save("contacts.xml");
7. Nuances utiles
Comparaison de LINQ to XML avec d'autres approches (.NET)
| Approche | Quand utiliser | Avantages | Inconvénients |
|---|---|---|---|
|
Code ancien, legacy | Intégration avec XPath | Beaucoup de code, API lourde |
|
Pour la sérialisation d'objets | Facile de sérialiser C# ↔ XML | Faible flexibilité |
|
Tout le reste : parsing, édition | Linéal, concis, puissant, LINQ | Consomme un peu plus de mémoire |
Application dans des projets réels et en entretien
- Parsing de fichiers de configuration où il n'y a pas de correspondance claire avec des classes C#.
- Import/export de données (par ex. export/import des données utilisateurs).
- Migrations, traitement de XML "sale" ou complexe.
- Transformations rapides (par ex. convertir un format XML en un autre).
"Pourquoi je dois connaître ça ?" — en entretien on peut vous demander de transformer ou parser un XML complexe, et avec LINQ to XML vous le ferez plus simplement et proprement que avec les anciens API.
8. Erreurs typiques en travaillant avec LINQ to XML
Erreur n°1 : tenter d'accéder à un élément absent sans vérification.
Si un élément peut être absent, utilisez l'opérateur ?. ou vérifiez préalablement contre null. Sinon vous obtiendrez une NullReferenceException et un plantage inattendu à l'exécution.
Erreur n°2 : confusion entre attributs et éléments.
Les attributs et les éléments sont des entités différentes en XML. Parfois les données sont réparties entre eux. Par exemple :
<User login="admin"><Status>active</Status></User>
Pour obtenir la valeur de l'attribut login, utilisez .Attribute("login"), et pour obtenir le contenu du tag <Status> — .Element("Status").
Erreur n°3 : mauvaise compréhension du comportement de la méthode .Remove().
La méthode .Remove() modifie immédiatement la structure du document XML en mémoire. Elle ne retourne rien, et il n'est pas possible d'annuler la suppression ensuite. Les débutants s'attendent parfois à ce qu'elle renvoie les éléments supprimés ou qu'elle signale le changement — ce n'est pas le cas.
GO TO FULL VERSION