9

次のコードを検討してください。

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 Express 2010で実行すると、予想どおり例外が発生しません。繰り返しながらリストから何かを削除しているので、1つ期待します。

私が取得するのは、最初の子ノードのみが削除されたリストです。

ここに画像の説明を入力

無効な操作の例外が発生しないのはなぜですか?

IDEOne.com の同等のコードでは、予想される例外が発生することに注意してください: http://ideone.com/qoRBbb

また、すべての LINQ ( .Cast().Where()) を削除すると、同じ結果が得られます。1 つのノードのみが削除され、例外はありません。

VSExpress の設定に問題はありますか?


遅延実行が関係していることは知っていますが、ソース列挙 (子メモ) を反復するために where 句が反復されると予想されることに注意してください。これにより、期待している例外が発生します。

私の問題は、VSexpress では例外が発生しないが、IDEOne では発生することです (両方/すべてのケースで例外が発生するか、少なくともそうでない場合は正しい結果が期待されます)。


Wouter の回答から、例外を与えるのではなく、最初の子が削除されたときにイテレータを無効にしているようです。公式にそう言っているものはありますか?この動作は他の場合に予想されますか? 「サイレントだが致命的」という例外ではなく、サイレントにイテレータを無効にすることを呼びます。

4

2 に答える 2

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);
}
于 2013-03-28T13:12:24.713 に答える
2

次のコードでも例外はスローされません。

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

そして、それはちょうど 1 つの要素を削除します。私の説明が 100% 正しいというわけではありませんが、正しい方向に進んでいます。コレクションを反復処理すると、その列挙子が取得されます。コレクションである XmlNode の場合、これは と呼ばれるカスタム クラスXmlChildEnumeratorです。

Reflector を介して MoveNext 実装を調べると、列挙子が現在見ているノードを覚えていることがわかります。MoveNext を呼び出すと、次の兄弟に移動します。

上記のコードでは、コレクションから最初のノードを取得しています。foreach ループの本体で暗黙的に生成された列挙子は、その最初のノードを現在のノードとして受け取ります。次に、foreach ループの本体でそのノードを削除します。

これで、そのノードがリストから切り離され、実行が MoveNext の呼び出しに移ります。ただし、コレクションから最初のノードを削除したばかりなので、コレクションから切り離され、ノードには兄弟がありません。ノードには兄弟がないため、反復が停止して foreach ループが終了し、1 つの要素のみが削除されます。

コレクションが変更されたかどうかをチェックしないため、これは例外をスローしません。見つけられる次のノードに移動したいだけです。しかし、削除された (デタッチされた) ノードはコレクションに属していないため、ループは停止します。

これで問題が解決することを願っています。

于 2013-03-28T13:55:08.493 に答える