C#中使用SelectNodes篩選XML元素的問題


今天在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,導致程序在運行的時候和調試的時候表現不同。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM