Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
LINQ to XML contient différentes méthodes qui vous permettent de modifier directement une arborescence XML. Vous pouvez ajouter des éléments, supprimer des éléments, modifier le contenu d’un élément, ajouter des attributs, et ainsi de suite. Cette interface de programmation est décrite dans Modifier les arbres XML. Si vous effectuez une itération à travers l'un des axes, tels que Elements, et que vous modifiez l’arborescence XML au fur et à mesure, cela peut conduire à des bogues étranges.
Ce problème est parfois appelé « Le problème d’Halloween ».
Lorsque vous écrivez du code à l’aide de LINQ qui itère dans une collection, vous écrivez du code dans un style déclaratif. Il est plus semblable à décrire ce que vous voulez, plutôt que la façon dont vous voulez le faire. Si vous écrivez du code qui 1) obtient le premier élément, 2) teste-le pour une condition, 3) le modifie et 4) le place dans la liste, alors il s’agit d’un code impératif. Vous dites à l’ordinateur comment faire ce que vous voulez faire.
Le mélange de ces styles de code dans la même opération est ce qui entraîne des problèmes. Tenez compte des éléments suivants :
Supposons que vous disposez d’une liste liée avec trois éléments (a, b et c) :
a -> b -> c
Supposons maintenant que vous souhaitez parcourir la liste liée, en ajoutant trois nouveaux éléments (a', b' et c'). Vous souhaitez que la liste liée résultante ressemble à ceci :
a -> a' -> b -> b' -> c -> c'
Vous écrivez donc du code qui itère dans la liste, et pour chaque élément, ajoute un nouvel élément juste après lui. Ce qui se passe, c’est que votre code verra d’abord l’élément a et insérera a' après celui-ci. Maintenant, votre code passe au nœud suivant de la liste, qui est maintenant a', il ajoute donc un nouvel élément entre a' et b à la liste !
Comment résoudre ce problème ? Eh bien, vous pouvez créer une copie de la liste liée d’origine et créer une liste complètement nouvelle. Ou si vous écrivez du code purement impératif, vous pouvez trouver le premier élément, ajouter le nouvel élément, puis avancer deux fois dans la liste liée, avancer sur l’élément que vous venez d’ajouter.
Exemple : Ajout lors de l’itération
Par exemple, supposons que vous souhaitiez écrire du code pour créer un doublon de chaque élément d’une arborescence :
XElement root = new XElement("Root",
new XElement("A", "1"),
new XElement("B", "2"),
new XElement("C", "3")
);
foreach (XElement e in root.Elements())
root.Add(new XElement(e.Name, (string)e));
Dim root As XElement = _
<Root>
<A>1</A>
<B>2</B>
<C>3</C>
</Root>
For Each e As XElement In root.Elements()
root.Add(New XElement(e.Name, e.Value))
Next
Ce code passe dans une boucle infinie. L’instruction foreach itère le long de l’axe Elements(), ajoutant de nouveaux éléments à l’élément doc. Elle finit par itérer également au sein des éléments qu'elle vient d'ajouter. Et parce qu’il alloue de nouveaux objets avec chaque itération de la boucle, il consommera finalement toute la mémoire disponible.
Vous pouvez résoudre ce problème en extrayant la collection en mémoire à l’aide de l’opérateur ToList de requête standard, comme suit :
XElement root = new XElement("Root",
new XElement("A", "1"),
new XElement("B", "2"),
new XElement("C", "3")
);
foreach (XElement e in root.Elements().ToList())
root.Add(new XElement(e.Name, (string)e));
Console.WriteLine(root);
Dim root As XElement = _
<Root>
<A>1</A>
<B>2</B>
<C>3</C>
</Root>
For Each e As XElement In root.Elements().ToList()
root.Add(New XElement(e.Name, e.Value))
Next
Console.WriteLine(root)
Maintenant, le code fonctionne. L’arborescence XML résultante est la suivante :
<Root>
<A>1</A>
<B>2</B>
<C>3</C>
<A>1</A>
<B>2</B>
<C>3</C>
</Root>
Exemple : suppression lors de l’itération
Si vous souhaitez supprimer tous les nœuds à un certain niveau, vous pouvez être tenté d’écrire du code comme suit :
XElement root = new XElement("Root",
new XElement("A", "1"),
new XElement("B", "2"),
new XElement("C", "3")
);
foreach (XElement e in root.Elements())
e.Remove();
Console.WriteLine(root);
Dim root As XElement = _
<Root>
<A>1</A>
<B>2</B>
<C>3</C>
</Root>
For Each e As XElement In root.Elements()
e.Remove()
Next
Console.WriteLine(root)
Toutefois, cela ne fait pas ce que vous voulez. Dans ce cas, une fois que vous avez supprimé le premier élément, A, il est supprimé de l’arborescence XML contenue dans la racine, et le code de la méthode Elements qui effectue l’itération ne peut pas trouver l’élément suivant.
Cet exemple génère la sortie suivante :
<Root>
<B>2</B>
<C>3</C>
</Root>
La solution est encore une fois d’appeler ToList pour matérialiser la collection, comme suit :
XElement root = new XElement("Root",
new XElement("A", "1"),
new XElement("B", "2"),
new XElement("C", "3")
);
foreach (XElement e in root.Elements().ToList())
e.Remove();
Console.WriteLine(root);
Dim root As XElement = _
<Root>
<A>1</A>
<B>2</B>
<C>3</C>
</Root>
For Each e As XElement In root.Elements().ToList()
e.Remove()
Next
Console.WriteLine(root)
Cet exemple génère la sortie suivante :
<Root />
Vous pouvez également éliminer complètement l’itération en appelant RemoveAll sur l’élément parent.
XElement root = new XElement("Root",
new XElement("A", "1"),
new XElement("B", "2"),
new XElement("C", "3")
);
root.RemoveAll();
Console.WriteLine(root);
Dim root As XElement = _
<Root>
<A>1</A>
<B>2</B>
<C>3</C>
</Root>
root.RemoveAll()
Console.WriteLine(root)
Exemple : Pourquoi LINQ ne peut pas gérer automatiquement ces problèmes
Une approche consisterait à toujours tout placer en mémoire au lieu d'effectuer une évaluation différée. Toutefois, il serait très coûteux en termes de performances et d’utilisation de la mémoire. En fait, si LINQ, et LINQ to XML, devaient adopter cette approche, elle échouerait dans des situations réelles.
Une autre approche possible consisterait à placer une sorte de syntaxe de transaction dans LINQ et à faire en sorte que le compilateur tente d’analyser le code pour déterminer si une collection particulière doit être matérialisée. Toutefois, la tentative de déterminer tout le code qui a des effets secondaires est incroyablement complexe. Considérez le code suivant :
var z =
from e in root.Elements()
where TestSomeCondition(e)
select DoMyProjection(e);
Dim z = _
From e In root.Elements() _
Where (TestSomeCondition(e)) _
Select DoMyProjection(e)
Ce code d’analyse doit analyser les méthodes TestSomeCondition et DoMyProjection, ainsi que toutes les méthodes appelées, pour déterminer si un code avait des effets secondaires. Mais le code d’analyse ne pouvait pas simplement rechercher du code qui avait des effets secondaires. Il faudrait sélectionner uniquement le code qui aura des effets secondaires sur les éléments enfants de root dans cette situation.
LINQ to XML ne tente pas d’effectuer une telle analyse. C’est à vous d’éviter ces problèmes.
Exemple : Utiliser du code déclaratif pour générer une nouvelle arborescence XML plutôt que de modifier l’arborescence existante
Pour éviter de tels problèmes, ne mélangez pas de code déclaratif et impératif, même si vous connaissez exactement la sémantique de vos collections et la sémantique des méthodes qui modifient l’arborescence XML. Si vous écrivez du code qui évite les problèmes, votre code doit être géré par d’autres développeurs à l’avenir, et ils peuvent ne pas être aussi clairs sur les problèmes. Si vous mélangez des styles de codage déclaratifs et impératifs, votre code sera plus fragile. Si vous écrivez du code qui matérialise une collection afin que ces problèmes soient évités, notez-le avec des commentaires appropriés dans votre code, afin que les programmeurs de maintenance comprennent le problème.
Si les performances et d’autres considérations autorisent, utilisez uniquement du code déclaratif. Ne modifiez pas votre arborescence XML existante. Au lieu de cela, générez-en un nouveau, comme illustré dans l’exemple suivant :
XElement root = new XElement("Root",
new XElement("A", "1"),
new XElement("B", "2"),
new XElement("C", "3")
);
XElement newRoot = new XElement("Root",
root.Elements(),
root.Elements()
);
Console.WriteLine(newRoot);
Dim root As XElement = _
<Root>
<A>1</A>
<B>2</B>
<C>3</C>
</Root>
Dim newRoot As XElement = New XElement("Root", _
root.Elements(), root.Elements())
Console.WriteLine(newRoot)