Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
A LINQ–XML különböző metódusokat tartalmaz, amelyek lehetővé teszik az XML-fa közvetlen módosítását. Hozzáadhat elemeket, törölheti az elemeket, módosíthatja egy elem tartalmát, attribútumokat adhat hozzá stb. Ezt a programozási felületet az XML-fák módosítása című témakörben ismertetjük. Ha iterál az egyik tengelyen, például Elements, és az iteráció során módosítja az XML-fát, akkor furcsa hibákat találhat.
Ezt a problémát néha "Halloween-problémának" is nevezik.
Amikor linq használatával ír egy kódot, amely egy gyűjteményen keresztül iterál, deklaratív stílusban ír kódot. Inkább azt írja le, amit szeretne, semmint azt, hogy hogyan szeretné elvégezni. Ha olyan kódot ír, amely 1) lekéri az első elemet, 2) teszteli valamilyen feltételt, 3) módosítja, és 4) visszaírja a listába, akkor ez imperatív kód lenne. Azt mondja a számítógépnek , hogyan tegye, amit akar.
Ha ugyanabban a műveletben keveri ezeket a kódstílusokat, az problémákhoz vezet. Vegye figyelembe a következőket:
Tegyük fel, hogy van egy csatolt lista, amelyben három elem szerepel (a, b és c):
a -> b -> c
Tegyük fel, hogy át szeretne lépni a csatolt listán, és három új elemet (a', b' és c) szeretne hozzáadni. Azt szeretné, hogy az eredményként kapott csatolt lista a következőképpen nézzen ki:
a -> a' -> b -> b' -> c -> c'
Úgy írja meg a kódot, hogy az végigiterál a listán, és minden elem után közvetlenül hozzáad egy újat. Az történik, hogy a kódod először a a elemet látja, majd beszúrja a a' elemet utána. A kód most a listában a következő elemre lép, amely most a', így új elemet ad az 'a' és 'b' közé a listában!
Hogyan oldaná meg ezt? Lehet, hogy másolatot készít az eredeti csatolt listáról, és létrehoz egy teljesen új listát. Vagy ha tisztán imperatív kódot ír, megtalálhatja az első elemet, hozzáadhatja az új elemet, majd kétszer továbbléphet a csatolt listában, és továbbléphet az imént hozzáadott elem fölé.
Példa: Hozzáadás iterálás közben
Tegyük fel például, hogy egy fa minden elemének duplikátumát tartalmazó kódot szeretne írni:
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
Ez a kód egy végtelen hurokba kerül. Az foreach utasítás végigvezeti a Elements() tengelyt, és új elemeket ad hozzá az doc elemhez. Végül az imént hozzáadott elemeken keresztül is iterál. Mivel a ciklus minden iterációjával új objektumokat foglal le, végül az összes rendelkezésre álló memóriát felhasználja.
Ezt a problémát úgy háríthatja el, hogy a gyűjteményt a szokásos lekérdezési operátorral a ToList memóriába húzza, az alábbiak szerint:
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)
Most már működik a kód. Az eredményként kapott XML-fa a következő:
<Root>
<A>1</A>
<B>2</B>
<C>3</C>
<A>1</A>
<B>2</B>
<C>3</C>
</Root>
Példa: Törlés iterálás közben
Ha egy adott szinten szeretné törölni az összes csomópontot, előfordulhat, hogy a következőhöz hasonló kódot kell írnia:
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)
Ez azonban nem azt teszi, amit szeretne. Ebben a helyzetben, miután eltávolította az első elemet, az A-t, az eltávolításra kerül a "root"-ban található XML-fáról, és az iterálást végző Elements metódus kódja nem találja meg a következő elemet.
Ez a példa a következő kimenetet hozza létre:
<Root>
<B>2</B>
<C>3</C>
</Root>
A megoldás az, hogy újra meghívjuk a ToList-t a gyűjtemény anyagiasításához, az alábbiak szerint:
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)
Ez a példa a következő kimenetet hozza létre:
<Root />
Az iterációt teljesen megszüntetheti a szülőelem meghívásával a RemoveAll-on:
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)
Példa: Miért nem tudja a LINQ automatikusan kezelni ezeket a problémákat?
Az egyik megközelítés az lenne, hogy mindig mindent a memóriába vinni, ahelyett, hogy lusta értékelést alkalmaznánk. A teljesítmény és a memóriahasználat szempontjából azonban nagyon költséges lenne. Valójában, ha a LINQ és a LINQ az XML-hez hasonló megközelítést alkalmazna, az valós helyzetekben meghiúsulna.
Egy másik lehetséges módszer az lenne, ha valamilyen tranzakciós szintaxist helyezne a LINQ-ba, és a fordító megpróbálja elemezni a kódot annak megállapításához, hogy egy adott gyűjteményt szükséges-e materializálni. A mellékhatásokat tartalmazó kódok meghatározása azonban rendkívül összetett. Vegye figyelembe a következő kódot:
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)
Az ilyen elemzési kódnak elemeznie kell a TestSomeCondition és a DoMyProjection metódusokat, valamint az összes metódust, amelyet ezek a metódusok hívtak, annak megállapításához, hogy bármelyik kódnak voltak-e mellékhatásai. Az elemzési kód azonban nem csak a mellékhatásokat okozó kódot tudta keresni. Ebben a helyzetben ki kell választania azt a kódot, amely mellékhatásokat gyakorolt a root gyermekelemekre.
A LINQ-ből XML-be nem próbál ilyen elemzést végezni. Rajtad múlik, hogy elkerüld ezeket a problémákat.
Példa: Deklaratív kód használata új XML-fa létrehozásához a meglévő fa módosítása helyett
Az ilyen problémák elkerülése érdekében ne keverje a deklaratív és imperatív kódot, még akkor sem, ha pontosan ismeri a gyűjtemények szemantikáját és az XML-fát módosító módszerek szemantikáját. Ha olyan kódot ír, amely elkerüli a problémákat, a kódot a jövőben más fejlesztőknek kell fenntartaniuk, és előfordulhat, hogy nem lesznek ilyen egyértelműek a problémákban. Ha deklaratív és imperatív kódolási stílusokat kever, a kód törékenyebb lesz. Ha olyan kódot ír, amely a problémák elkerülése érdekében létrehoz egy gyűjteményt, jegyezze fel a kódban megfelelő megjegyzésekkel, hogy a karbantartási programozók megértsék a problémát.
Ha a teljesítmény és más szempontok megengedik, csak deklaratív kódot használjon. Ne módosítsa a meglévő XML-fát. Ehelyett hozzon létre egy újat az alábbi példában látható módon:
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)