Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
LINQ till XML innehåller olika metoder som gör att du kan ändra ett XML-träd direkt. Du kan lägga till element, ta bort element, ändra innehållet i ett element, lägga till attribut och så vidare. Det här programmeringsgränssnittet beskrivs i Ändra XML-träd. Om du itererar genom en av axlarna, till exempel Elements, och du ändrar XML-trädet när du itererar genom axeln, kan du få några konstiga buggar.
Det här problemet kallas ibland "Halloween-problemet".
När du skriver kod med LINQ som itererar via en samling skriver du kod i deklarativ stil. Det är mer likt att beskriva vad du vill, snarare än hur du vill få det gjort. Om du skriver kod som 1) hämtar det första elementet, 2) testar det för vissa villkor, 3) ändrar det och 4) placerar tillbaka det i listan, då skulle detta vara imperativ kod. Du berättar för datorn hur du gör vad du vill.
Att blanda dessa kodformat i samma åtgärd är det som leder till problem. Tänk på följande:
Anta att du har en länkad lista med tre objekt i den (a, b och c):
a -> b -> c
Anta nu att du vill gå igenom den länkade listan och lägga till tre nya objekt (a', b' och c'). Du vill att den länkade listan ska se ut så här:
a -> a' -> b -> b ' -> c -> c'
Så du skriver kod som itererar genom listan och lägger till ett nytt objekt direkt efter varje objekt. Vad som händer är att koden först ser elementet a och infogar a' efter det. Nu flyttas koden till nästa nod i listan, som nu a'är , så den lägger till ett nytt objekt mellan a och b i listan!
Hur skulle du lösa det här? Du kan göra en kopia av den ursprungliga länkade listan och skapa en helt ny lista. Eller om du skriver ren imperativ kod kan du hitta det första objektet, lägga till det nya objektet och sedan gå vidare två gånger i den länkade listan och avancera över det element som du nyss lade till.
Exempel: Lägga till vid iterering
Anta till exempel att du vill skriva kod för att skapa en dubblett av varje element i ett träd:
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
Den här koden hamnar i en oändlig loop.
foreach-instruktionen itererar genom axeln Elements() och lägger till nya element i doc-elementet. Det slutar iterera också genom de element som det nyligen lade till. Och eftersom det allokerar nya objekt med varje iteration av loopen, kommer den så småningom att förbruka allt tillgängligt minne.
Du kan åtgärda det här problemet genom att hämta samlingen i minnet med hjälp av ToList standardfrågeoperatorn enligt följande:
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)
Nu fungerar koden. Det resulterande XML-trädet är följande:
<Root>
<A>1</A>
<B>2</B>
<C>3</C>
<A>1</A>
<B>2</B>
<C>3</C>
</Root>
Exempel: Ta bort vid iterering
Om du vill ta bort alla noder på en viss nivå kan du vara frestad att skriva kod som följande:
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)
Det här gör dock inte det du vill. I det här fallet, när du har tagit bort det första elementet A, tas det bort från XML-trädet som finns i roten och koden i metoden Element som itererar kan inte hitta nästa element.
Det här exemplet genererar följande utdata:
<Root>
<B>2</B>
<C>3</C>
</Root>
Lösningen är återigen att anropa ToList för att materialisera samlingen enligt följande:
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)
Det här exemplet genererar följande utdata:
<Root />
Du kan också eliminera iterationen helt och hållet genom att anropa RemoveAll det överordnade elementet:
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)
Exempel: Varför LINQ inte kan hantera dessa problem automatiskt
En metod skulle vara att alltid föra in allt i minnet i stället för att göra lat utvärdering. Det skulle dock vara mycket dyrt när det gäller prestanda och minnesanvändning. Om LINQ och LINQ till XML skulle använda den här metoden skulle det faktiskt misslyckas i verkliga situationer.
En annan möjlig metod skulle vara att placera någon form av transaktionssyntax i LINQ och låta kompilatorn försöka analysera koden för att avgöra om någon viss samling behövde materialiseras. Men att försöka fastställa all kod som har biverkningar är otroligt komplext. Överväg följande kod:
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)
Sådan analyskod skulle behöva analysera metoderna TestSomeCondition och DoMyProjection, och alla metoder som dessa metoder anropade, för att avgöra om någon kod hade biverkningar. Men analyskoden kunde inte bara leta efter någon kod som hade biverkningar. Det skulle behöva välja bara den kod som hade effekter på de underordnade elementen av root i den här situationen.
LINQ till XML försöker inte göra någon sådan analys. Det är upp till dig att undvika dessa problem.
Exempel: Använd deklarativ kod för att generera ett nytt XML-träd i stället för att ändra det befintliga trädet
Undvik sådana problem genom att inte blanda deklarativ och imperativ kod, även om du känner till exakt semantiken i dina samlingar och semantiken för de metoder som ändrar XML-trädet. Om du skriver kod som undviker problem måste koden underhållas av andra utvecklare i framtiden, och de kanske inte är lika tydliga i problemen. Om du blandar deklarativa och imperativa kodningsformat blir koden mer skör. Om du skriver kod som materialiserar en samling så att dessa problem undviks kan du notera den med kommentarer efter behov i koden, så att underhållsprogrammarna förstår problemet.
Om prestanda och andra överväganden tillåter använder du endast deklarativ kod. Ändra inte ditt befintliga XML-träd. Generera i stället en ny som visas i följande exempel:
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)