Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
LINQ ke XML berisi berbagai metode yang memungkinkan Anda mengubah pohon XML secara langsung. Anda dapat menambahkan elemen, menghapus elemen, mengubah konten elemen, menambahkan atribut, dan sebagainya. Antarmuka pemrograman ini dijelaskan dalam Memodifikasi pohon XML. Jika Anda melakukan iterasi melalui salah satu sumbu, seperti Elements, dan Anda memodifikasi pohon XML saat melakukan iterasi melalui sumbu, Anda dapat berakhir dengan beberapa bug aneh.
Masalah ini terkadang dikenal sebagai "Masalah Halloween".
Saat Anda menulis beberapa kode menggunakan LINQ yang melakukan iterasi melalui koleksi, Anda menulis kode dalam gaya deklaratif. Lebih mirip menggambarkan apa yang Anda inginkan, melainkan bagaimana Anda ingin menyelesaikannya. Jika Anda menulis kode yang 1) mendapatkan elemen pertama, 2) mengujinya untuk beberapa kondisi, 3) memodifikasinya, dan 4) menempatkannya kembali ke dalam daftar, maka ini akan menjadi kode imperatif. Anda memberi tahu komputer bagaimana melakukan apa yang ingin Anda lakukan.
Mencampur gaya kode ini dalam operasi yang sama adalah hal yang menyebabkan masalah. Pertimbangkan hal berikut:
Misalkan Anda memiliki daftar tertaut dengan tiga item di dalamnya (a, b, dan c):
a -> b -> c
Sekarang, misalkan Anda ingin menelusuri daftar tertaut, menambahkan tiga item baru (a', b', dan c'). Anda ingin daftar tertaut yang dihasilkan terlihat seperti ini:
a -> a' -> b -> b' -> c -> c'
Jadi Anda menulis kode yang berulang melalui daftar, dan untuk setiap item, menambahkan item baru tepat setelahnya. Yang terjadi adalah kode Anda akan pertama-tama melihat elemen a, dan menyisipkan a' setelahnya. Sekarang, kode Anda akan berpindah ke simpul berikutnya dalam daftar, yang sekarang a', sehingga menambahkan item baru antara a' dan b ke daftar!
Bagaimana anda memecahkan masalah ini? Nah, Anda mungkin membuat salinan daftar asli yang ditautkan, dan membuat daftar yang benar-benar baru. Atau jika Anda menulis kode murni imperatif, Anda mungkin menemukan item pertama, menambahkan item baru, dan kemudian maju dua kali dalam daftar tertaut, melaju di atas elemen yang baru saja Anda tambahkan.
Contoh: Menambahkan saat melakukan iterasi
Misalnya, Anda ingin menulis kode untuk membuat duplikat setiap elemen di pohon:
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
Kode ini berjalan dalam perulangan tak terbatas. Pernyataan foreach berulang melalui sumbu Elements(), menambahkan elemen baru pada elemen doc. Akhirnya melakukan iterasi juga terhadap elemen-elemen yang baru saja ditambahkan. Dan karena mengalokasikan objek baru dengan setiap iterasi perulangan, pada akhirnya akan mengonsumsi semua memori yang tersedia.
Anda dapat memperbaiki masalah ini dengan menarik koleksi ke dalam memori menggunakan ToList operator kueri standar, sebagai berikut:
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)
Sekarang kodenya berfungsi. Pohon XML yang dihasilkan adalah sebagai berikut:
<Root>
<A>1</A>
<B>2</B>
<C>3</C>
<A>1</A>
<B>2</B>
<C>3</C>
</Root>
Contoh: Menghapus saat melakukan iterasi
Jika Anda ingin menghapus semua simpul pada tingkat tertentu, Anda mungkin tergoda untuk menulis kode seperti berikut:
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)
Namun, ini tidak melakukan apa yang Anda inginkan. Dalam situasi ini, setelah Anda menghapus elemen pertama, A, elemen tersebut dihapus dari pohon XML yang ada di root, dan kode dalam metode Elemen yang melakukan iterasi tidak dapat menemukan elemen berikutnya.
Contoh ini menghasilkan output berikut:
<Root>
<B>2</B>
<C>3</C>
</Root>
Solusinya lagi adalah memanggil ToList untuk mewujudkan koleksi, sebagai berikut:
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)
Contoh ini menghasilkan output berikut:
<Root />
Atau, Anda dapat menghilangkan iterasi sama sekali dengan memanggil RemoveAll elemen induk:
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)
Contoh: Mengapa LINQ tidak dapat menangani masalah ini secara otomatis
Salah satu pendekatannya adalah selalu membawa semuanya ke dalam memori alih-alih melakukan evaluasi tunda. Namun, akan sangat mahal dalam hal performa dan penggunaan memori. Bahkan, jika LINQ, dan LINQ ke XML, mengikuti pendekatan ini, maka itu akan gagal dalam situasi dunia nyata.
Pendekatan lain yang mungkin adalah menempatkan semacam sintaks transaksi ke dalam LINQ, dan memiliki kompilator mencoba menganalisis kode untuk menentukan apakah ada koleksi tertentu yang perlu diwujudkan. Namun, mencoba menentukan semua kode yang memiliki efek samping sangat kompleks. Pertimbangkan kode berikut:
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)
Kode analisis tersebut perlu menganalisis metode TestSomeCondition dan DoMyProjection, dan semua metode yang dipanggil metode tersebut, untuk menentukan apakah ada kode yang memiliki efek samping. Tetapi kode analisis tidak bisa hanya mencari kode apa pun yang memiliki efek samping. Diperlukan untuk memilih hanya kode yang berpengaruh pada elemen anak root dalam situasi ini.
LINQ ke XML tidak mencoba melakukan analisis tersebut. Terserah Anda untuk menghindari masalah ini.
Contoh: Gunakan kode deklaratif untuk menghasilkan pohon XML baru daripada memodifikasi pohon yang ada
Untuk menghindari masalah seperti itu, jangan mencampur kode deklaratif dan imperatif, bahkan jika Anda tahu persis semantik koleksi Anda dan semantik metode yang memodifikasi pohon XML. Jika Anda menulis kode yang menghindari masalah, kode Anda perlu dipertahankan oleh pengembang lain di masa depan, dan kode tersebut mungkin tidak sejelas masalah. Jika Anda mencampur gaya pengodean deklaratif dan imperatif, kode Anda akan lebih rapuh. Jika Anda menulis kode yang memfasilitasi terbentuknya koleksi untuk menghindari masalah ini, tandai dengan komentar yang tepat dalam kode Anda, supaya programmer perawatan bisa memahami masalah tersebut.
Jika performa dan pertimbangan lainnya memungkinkan, gunakan hanya kode deklaratif. Jangan ubah pohon XML yang sudah ada. Sebagai gantinya, buat yang baru seperti yang ditunjukkan dalam contoh berikut:
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)