0

次の XML があります。

<Parts>
  <Part name="Part1" disabled="true"></Part>
  <Part name="Part2" disabled="false"></Part>
  <Part name="Part3" ></Part>
  <Part name="Part4" disabled="true"></Part>  
</Parts>

disabled属性が に設定されているノードを削除したいtrue。'disabled' 属性がどの 'Part' 要素にも使用されていない場合、それは無効になっていないことを意味します。

私は次のコードを書きました:

XmlNode root = xmlDoc.DocumentElement;
List<XmlNode> disabledNodes = new List<XmlNode>();
foreach(XmlNode node in root.ChildNodes)
{
    if(node.Attributes["disabled"] != null && 
        Convert.ToBoolean(node.Attributes["disabled"].Value))
    {
        disabledNodes.Add(node);
    }
}

foreach (XmlNode node in disabledNodes)
{
    root .RemoveChild(node);
}

このコードは、予想どおり XML から 2 つのノードを削除します。

次に、コードをコンパクトにするために次のコードを書きました。

foreach (XmlNode node in root.ChildNodes.Cast<XmlNode>()
    .Where(child => child.Attributes["disabled"] != null && 
    Convert.ToBoolean(child.Attributes["disabled"].Value)))
{
    root.RemoveChild(node); // This line works fine without any exception.
}

このループは 1 回だけ繰り返され、XML からノードが 1 つだけ削除されることがわかりました。


編集された質問:

foreachループを変更すると、今度は LINQ 式の結果をList<T>usingToList()メソッドに変換します (@Toni Petrina の回答で提案されているように)。そして今回はうまくいきました!

 foreach (XmlNode node in root.ChildNodes.Cast<XmlNode>()
        .Where(child => child.Attributes["disabled"] != null && 
        Convert.ToBoolean(child.Attributes["disabled"].Value)).ToList())
    {
        root.RemoveChild(node); // This line works fine without any exception.
    }

ToList()作成されたLINQ式の使用がforeachループで期待どおりに機能するのはなぜですか? LINQ の結果が 2 つの異なる状況で異なる動作をする技術的な理由はありますか?

.NET 4.0 を使用しています。

4

2 に答える 2

3

あなたの問題は、コレクションを列挙するときにコレクションを変更することです。違います。次のようなものを使用する必要があります。

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

foreach (XmlNode node in disabledNodes)
{
    root.RemoveChild(node);
}

アップデート

これは、遅延実行によるものです。ToArray() または ToList() を使用しない場合、IEnumerator は次の要素が必要なとき (つまり、foreach が次のターンに進むとき) に値を 1 つずつ返します。そして、 foreach が最初のターンを実行すると、ソースが変更され、反復が停止します。ただし、ToArray() を呼び出すと、disabledNodes の配列を含む新しい変数が取得され、foreach は反復するコレクションを変更しません。

于 2013-03-28T12:20:37.243 に答える
0

書く:

foreach (XmlNode node in root.ChildNodes.Cast<XmlNode>()
    .Where(child => child.Attributes["disabled"] != null && 
    Convert.ToBoolean(child.Attributes["disabled"].Value)).ToList())
{
    root.RemoveChild(node);
}

追加の ToList() を追加して、LINQ 式の即時実行を強制しました。

LINQ クエリを作成すると、実際には結果を保持しない IEnumerable コレクションが取得されます。これらすべての Select 句と Where 句、およびその他の多くの句を作成したとしても、反復処理を開始する前に完全なクエリが実行されるわけではありません。そうして初めて、実際にクエリが実行されます。

元のコードでは、クエリを作成し、その反復処理を開始しました。すべての LINQ 句を通過する最初の項目を受け取り、最初のノードを削除しました。しかし、現在変更されているルート コレクションを反復していたため、反復が停止します。

foreach ループの本体で繰り返し処理しているコレクションを変更することはできません。

于 2013-03-28T12:14:04.117 に答える