今天在C#中使用SelectNodes的時候出現了一些怪現象,先從還原現場開始吧。
首先創建一個簡單的XML文件來試驗,還是就保存為test.xml
<?xml version="1.0" encoding="utf-8" ?>
<root>
<users job="salas">
<user>
<name>Joe</name>
<age>17</age>
</user>
<user>
<name>Kate</name>
<age>12</age>
</user>
<user>
<name>Parry</name>
<age>66</age>
</user>
<user>
<name>Qiqi</name>
<age>32</age>
</user>
</users>
<users job="developer">
<user>
<name>David</name>
<age>23</age>
</user>
<user>
<name>Eath</name>
<age>54</age>
</user>
</users>
</root>
下面是我的C#代碼
static void Main(string[] args)
{
XmlDocument doc = new XmlDocument();
doc.Load("test.xml");
XmlElement root = doc.DocumentElement;
XmlNode userCollection= root.SelectSingleNode("users[1]");
XmlNodeList usersOfOne = userCollection.SelectNodes("user");
XmlNode placeholder=doc.CreateElement("placeholder");
channel.ReplaceChild(placeholder, usersOfOne.Item(0));
Console.WriteLine(usersOfOne.Count);
}
代碼邏輯很簡單,就是想找到第一個<users>節點把它第一個<user>子節點替換一下。
關鍵在於替換之後的問題來了,我原本想的是usersOfOne的個數應該保存著4個<user>節點,但是最終的結果只有1個,而且就只是那個被替換掉的那個節點。
繼續試驗,這次修改下C#代碼,將替換的節點變成第二個<user>節點試試?usersOfOne的個數就變成兩個,包括第一和第二個節點。
研究SelectNodes源碼(以下源代碼都是在Reflector 6中查看的)
public XmlNodeList SelectNodes(string xpath)
{
XPathNavigator navigator = this.CreateNavigator();
if (navigator == null)
{
return null;
}
return new XPathNodeList(navigator.Select(xpath));
}
發現它返回一個XPathNodeList,再去看下它的構造函數
public XPathNodeList(XPathNodeIterator nodeIterator)
{
this.nodeIterator = nodeIterator;
this.list = new List<XmlNode>();
this.done = false;
}
你會發現它創建了一個List<XmlNode>,但是並沒有給它賦值。讓我們再去看看Count這個屬性
public override int Count
{
get
{
if (!this.done)
{
this.ReadUntil(0x7fffffff);
}
return this.list.Count;
}
}
它返回的數就是構造函數裡創建的List<XmlNode>的Count。再去看看Item()這個函數
public override XmlNode Item(int index)
{
if (this.list.Count <= index)
{
this.ReadUntil(index);
}
if ((index >= 0) && (this.list.Count > index))
{
return this.list[index];
}
return null;
}
同樣的也是返回的List<XmlNode>中的值。
所以,我們可以解釋上面實驗代碼中的怪現象了。
我們使用SelectNodes的時候,它並沒有真正的將節點取出來,而是當我們調用了其它方法後(比如item()或者Count屬性),才通過ReadUntil這個方法將它們的值保存到那個List<XmlNode>中。
Count這個屬性能將0x7fffffff個節點保存下來(這也暗示我們最多能處理的節點個數!?),而Item這個函數只是把你需要的個數保存下來,(大家也可以去看看ReadUntil方法)後面因為我將現在的這個節點替換了,所以在Count的時候,它無法去迭代找到下個節點,所以在替換第二個節點的時候只保留下第一第二節點的原因。
我們修改下上面的代碼如下:
static void Main(string[] args)
{
XmlDocument doc = new XmlDocument();
doc.Load("test.xml");
XmlElement root = doc.DocumentElement;
XmlNode channel = root.SelectSingleNode("users[1]");
XmlNodeList usersOfOne = channel.SelectNodes("user");
//在SelectNodes之後馬上調用Count
Console.WriteLine(usersOfOne.Count);
XmlNode placeholder=doc.CreateElement("placeholder");
channel.ReplaceChild(placeholder, usersOfOne.Item(0));
Console.WriteLine(usersOfOne.Count);
}
它就會像我預期的那樣打印出結果了。盡管替換節點之後,輸出的依然是4。
這個問題在調試的時候也比較難發現,因為你調試時查看usersOfOne.Count屬性,相當於在源程序中執行了Count一樣,所以在調試程序的時候,它會輸出的結果也是4,導致程序在運行的時候和調試的時候表現不同。
摘自 大眾.NET