2013-03-28 25 views
9

考慮下面的代碼:LINQ中,將XMLNode,的foreach的和異常

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Xml; 
using System.Xml.Linq; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      XmlDocument xmlDoc = new XmlDocument(); 

      xmlDoc.LoadXml(@"<Parts> 
    <Part name=""DisappearsOk"" disabled=""true""></Part> 
    <Part name=""KeepMe"" disabled=""false""></Part> 
    <Part name=""KeepMe2"" ></Part> 
    <Part name=""ShouldBeGone"" disabled=""true""></Part> 
</Parts>"); 

      XmlNode root = xmlDoc.DocumentElement; 
      List<XmlNode> disabledNodes = new List<XmlNode>(); 

      try 
      { 

       foreach (XmlNode node in root.ChildNodes.Cast<XmlNode>() 
              .Where(child => child.Attributes["disabled"] != null && 
                  Convert.ToBoolean(child.Attributes["disabled"].Value))) 
       { 
        Console.WriteLine("Removing:"); 
        Console.WriteLine(XDocument.Parse(node.OuterXml).ToString()); 
        root.RemoveChild(node); 
       } 
      } 
      catch (Exception Ex) 
      { 
       Console.WriteLine("Exception, as expected"); 
      } 

      Console.WriteLine(); 
      Console.WriteLine(XDocument.Parse(root.OuterXml).ToString()); 

      Console.ReadKey(); 
     } 
    } 
} 

當我在運行此代碼的Visual Studio 2010表達我沒有得到一個異常,符合市場預期。我希望有一個,因爲我在迭代時從列表中刪除某些東西。

我要做的得到的是一個列表返回僅在第一個子節點刪除:

enter image description here

爲什麼我沒有得到一個無效的操作異常?

注意,在IDEOne.com的equivilent代碼確實給預期的異常http://ideone.com/qoRBbb

還要注意的是,如果我刪除所有的LINQ(.Cast().Where())我得到相同的結果,只有一個節點中刪除, 沒有例外。

在VSExpress中我的設置​​有問題嗎?


注意,我知道延遲執行涉及,但是我希望在where子句中,當迭代時在源枚舉(子注),這將使我預期的異常進行迭代。我的問題是,我沒有在VSexpress中得到這個異常,但是在IDEOne中(我期望它在兩種/所有情況下,或者至少如果不是這樣,我期望得到正確的結果)。


Wouter's answer似乎當第一個孩子被刪除,但是無效的迭代器,而不是讓一個例外。有什麼官員說這個嗎?這種行爲在其他情況下是可以預料的嗎?我會無聲地調用迭代器而不是用「沉默但致命」的異常。

+0

我在結果中看不到'Console.WriteLine(「Removing:」);'的輸出。你確定這個循環觸發了嗎? – Impworks

+0

頂部 – IronMan84

+0

對不起,已編輯截圖(在初始測試後添加了檢查/演示) –

回答

2

即使下面的代碼不會引發任何異常:

foreach (XmlNode node in root.ChildNodes) 
    root.RemoveChild(node); 

而且它會刪除只有一個元件。我不是100%,我的解釋是正確的,但它是正確的。當你迭代一個集合時,你檢索它的枚舉器。對於集合XmlNode,這是一個名爲XmlChildEnumerator的自定義類。

如果您要通過Reflector查找MoveNext實現,您會看到枚舉器會記住它當前正在查看的節點。當您調用MoveNext時,您將轉到下一個兄弟。

上述代碼中發生的事情是,您從集合中獲取第一個節點。在foreach循環體中隱式生成的枚舉器將該第一個節點作爲其當前節點。然後,在foreach循環的主體中刪除該節點。

現在該節點已從列表中分離出來,並且執行再次移至調用MoveNext。但是,由於我們剛剛從集合中刪除了第一個節點,因此它從集合中分離出來,並且節點沒有兄弟節點。由於節點沒有兄弟節點,因此迭代停止並且foreach循環退出,從而僅刪除單個元素。

這不會引發異常,因爲它不會檢查集合是否已更改,而只是想要轉到它可以找到的下一個節點。但是由於移除(分離)的節點不屬於集合,所以循環停止。

希望能夠解決這個問題。

+0

優秀的解釋。顯然,莫諾人必須讓枚舉人編碼略有不同,才能引發例外(無論是否有意)。 –

2

因爲您正在迭代ChildNodes,所以刪除第一個子代會使迭代器失效。因此,迭代將在第一次移除後停止。

如果拆分您的篩選和迭代,你的代碼將刪除所有項目:

var col = root.ChildNodes.Cast<XmlNode>() 
          .Where(child => child.Attributes["disabled"] != null && 
              Convert.ToBoolean(child.Attributes["disabled"].Value)).ToList(); 

foreach (XmlNode node in col) 
{ 
    Console.WriteLine("Removing:"); 
    Console.WriteLine(XDocument.Parse(node.OuterXml).ToString()); 
    root.RemoveChild(node); 
} 
+0

當然,where子句仍然會導致底層集合被迭代? –

+2

但是'哪裏'應該被懶惰評估。爲什麼它會創建一個單獨的集合,而不是從'root.ChildNodes'流數據? – Impworks

+0

在LINQPad 4中試過了上述內容,作品 – chridam