2013-03-28 13 views
9

Rozważmy następujący kod:LINQ XmlNodes, foreach tych i wyjątki

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(); 
     } 
    } 
} 

Kiedy uruchomić ten kod w visual studio wyrażania 2010 Nie rozumiem wyjątek, zgodnie z oczekiwaniami. Spodziewałbym się jednego, ponieważ usuwam coś z listy podczas iteracji.

Co zrobić dostać to lista z powrotem tylko usuwa pierwszy węzeł dziecko:

enter image description here

Dlaczego nie otrzymuję wyjątek niepoprawnego działania?

Należy pamiętać, że kod equivilent w IDEOne.com daje oczekiwany wyjątek: http://ideone.com/qoRBbb

Należy również zauważyć, że jeśli usunąć wszystkie LINQ (.Cast().Where()) uzyskać ten sam rezultat, usunięto tylko jeden węzeł , bez wyjątku.

Czy jest jakiś problem z moimi ustawieniami w VSExpress?


Zauważ, że wiem, że wykonanie odroczony jest zaangażowany, ale czego można oczekiwać w przypadku gdy klauzula, kiedy powtórzyć po iteracyjne na wyliczenie źródłowego (uwaga dziecko), która dałaby wyjątek Czekam.

Mój problem polega na tym, że nie mam tego wyjątku w VSexpress, ale robię to w IDEOne (spodziewałbym się tego w obu/wszystkich przypadkach, a przynajmniej jeśli nie, oczekiwałbym poprawnego wyniku).


Od Wouter's answer wydaje się to unieważniania iterator kiedy pierwsze dziecko jest usuwany, a nie dając wyjątek. Czy jest coś oficjalnego, co mówi to? Czy tego zachowania można się spodziewać w innych przypadkach? Nazwałbym unieważnianie iteratora po cichu, a nie z wyjątkiem "Milczący, ale zabójczy".

+0

Nie widzę danych wyjściowych 'Console.WriteLine (" Usuwanie: ");' w twoim wyniku. Jesteś pewien, że pętla się w ogóle strzela? – Impworks

+0

Jest na górze – IronMan84

+0

Przepraszam, edytowany zrzut ekranu (dodano test/demonstrację po początkowym teście) –

Odpowiedz

2

Nawet poniższy kod nie będzie rzucać żadnych wyjątków:

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

I usunie dokładnie jeden element. Nie jestem w 100%, że moje wyjaśnienie jest poprawne, ale jest na dobrej drodze. Podczas wykonywania iteracji w kolekcji pobierasz moduł wyliczający. Dla XmlNode, który jest kolekcją, jest to niestandardowa klasa o nazwie XmlChildEnumerator.

Jeśli chcesz wyszukać implementację MoveNext za pomocą Reflectora, zobaczysz, że moduł wyliczający zapamiętuje węzeł, na który aktualnie patrzysz. Kiedy wywołujesz MoveNext, przechodzisz do następnego rodzeństwa.

Co dzieje się w powyższym kodzie jest to, że otrzymujesz pierwszy węzeł z kolekcji.Moduł wyliczający niejawnie wygenerowany w ciele pętli foreach przyjmuje pierwszy węzeł jako bieżący węzeł. Następnie, w ciele pętli foreach, usuwasz ten węzeł.

Odłączenie węzła od listy i przejście do wywołania MoveNext ponownie. Ponieważ jednak właśnie usunięto pierwszy węzeł z kolekcji, jest on odłączany od kolekcji, a węzeł nie ma siostrzanego. Ponieważ nie ma żadnego rodzeństwa dla węzła, iteracja zatrzymuje się i kończy pętlę foreach, usuwając w ten sposób tylko jeden element.

Nie powoduje to wyjątku, ponieważ nie sprawdza, czy kolekcja została zmieniona, po prostu chce przejść do następnego węzła, który może znaleźć. Ale ponieważ usunięty (odłączony) węzeł nie należy do kolekcji, pętla zatrzymuje się.

Mam nadzieję, że to rozwiąże problem.

+0

Doskonałe wyjaśnienie. Oczywiście ludzie Mono muszą mieć koder nieco inaczej, aby spowodować wyjątek (celowo lub nie). –

2

Ponieważ wykonujesz iterację po numerze ChildNodes, usunięcie pierwszego elementu podrzędnego powoduje unieważnienie iteratora. Z tego powodu iteracja zatrzyma się po pierwszym usunięciu.

Jeśli podzielić filtrowanie i iteracji, kod usunie wszystkie elementy:

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

Z pewnością klauzula where nadal powoduje, że kolekcja bazowa jest iterowana? –

+2

Ale 'Gdzie' ma być leniwy-oceniony. Dlaczego miałaby tworzyć osobną kolekcję, a nie przesyłać strumieniowo danych z 'root.ChildNodes'? – Impworks

+0

Wypróbowałem powyższe w LINQPad 4, działa – chridam