Почему XmlNodeList.Count возвращает противоречивые результаты после изменения документа XML? - PullRequest
1 голос
/ 17 января 2020

У меня есть XML документ, и я пытаюсь отделить узлы друг от друга. Мне нужен только узел root, затем только второй узел, а затем список узлов, существующих во вторых узлах. Я столкнулся с проблемой, когда при удалении узлов из второго узла или главного узла мой список становится пустым. Я не понимаю, почему это происходит, особенно из-за этого странного поведения ниже.

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

        doc.Load(@"C:\Users\Username\Desktop\Diagram.xml");

        XmlNode rootNode = doc.DocumentElement;
        XmlNode secondNode = doc.SelectSingleNode(rootNode.Name + "/root");

        XmlNodeList nodelist = doc.SelectNodes("//root/mxCell");

        Console.WriteLine("-----------------------------------------");
        Console.WriteLine(RemoveChildren(rootNode).OuterXml);
        Console.WriteLine("-----------------------------------------");
        Console.WriteLine(RemoveChildren(secondNode).OuterXml);
        Console.WriteLine("-----------------------------------------");
        //Console.WriteLine(rootNode.OuterXml);
        Console.WriteLine(nodelist.Count); //Becomes 0
        if (nodelist != null && nodelist.Count > 0)
        {
            foreach (XmlNode n in nodelist)
            {
                Console.WriteLine(n.OuterXml);
            }
        }

        Console.ReadLine();
    }

    private static XmlNode RemoveChildren(XmlNode n) {

        while (n.FirstChild != null)
        {
            n.RemoveChild(n.FirstChild);
        }

        return n;
    }
}

Если я выполню этот код, мой nodelist.count станет равным 0. Почему список узлов становится 0, но почему я все еще могу получить доступ ко второму узлу?

enter image description here

Однако, если я добавлю foreach l oop сразу после do c .SelectNodes ("// root / mxCell"); количество станет 4.

вот так,

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

        doc.Load(@"C:\Users\Username\Desktop\Diagram.xml");

        XmlNode rootNode = doc.DocumentElement;
        XmlNode secondNode = doc.SelectSingleNode(rootNode.Name + "/root");

        XmlNodeList nodelist = doc.SelectNodes("//root/mxCell");

        // Added code here
        if (nodelist != null && nodelist.Count > 0)
        {
            foreach (XmlNode n in nodelist)
            {
                Console.WriteLine(n.OuterXml);
            }
        }
        // End of added code

        Console.WriteLine("-----------------------------------------");
        Console.WriteLine(RemoveChildren(rootNode).OuterXml);
        Console.WriteLine("-----------------------------------------");
        Console.WriteLine(RemoveChildren(secondNode).OuterXml);
        Console.WriteLine("-----------------------------------------");
        //Console.WriteLine(rootNode.OuterXml);
        Console.WriteLine(nodelist.Count); //Becomes 4
        if (nodelist != null && nodelist.Count > 0)
        {
            foreach (XmlNode n in nodelist)
            {
                Console.WriteLine(n.OuterXml);
            }
        }

        Console.ReadLine();
    }

    private static XmlNode RemoveChildren(XmlNode n) {

        while (n.FirstChild != null)
        {
            n.RemoveChild(n.FirstChild);
        }

        return n;
    }
}

Теперь число равно 4.

enter image description here

Здесь используется xml:

<mxGraphModel dx="1086" dy="596" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
  <root>
    <mxCell id="0"/>
    <mxCell id="1" parent="0"/>
    <mxCell id="YJb7HCrh72y2aGPrfETQ-1" value="" style="endArrow=classic;html=1;" parent="1" edge="1">
      <mxGeometry width="50" height="50" relative="1" as="geometry">
        <mxPoint x="130" y="310" as="sourcePoint"/>
        <mxPoint x="180" y="260" as="targetPoint"/>
      </mxGeometry>
    </mxCell>
    <mxCell id="YJb7HCrh72y2aGPrfETQ-2" value="" style="endArrow=classic;html=1;" parent="1" edge="1">
      <mxGeometry width="50" height="50" relative="1" as="geometry">
        <mxPoint x="290" y="270" as="sourcePoint"/>
        <mxPoint x="340" y="220" as="targetPoint"/>
      </mxGeometry>
    </mxCell>
  </root>
</mxGraphModel>

1 Ответ

2 голосов
/ 17 января 2020

Поведение, которое вы наблюдаете, может быть продемонстрировано более просто следующим образом. Следующий модульный тест будет успешным (демонстрационная скрипка здесь ):

XmlNodeList nodelist = doc.SelectNodes("*/*"); // Select all children of the root node
RemoveChildren(doc.DocumentElement);
Assert.IsTrue(nodelist.Count == 0); // Passes

В то время как следующее не удастся:

XmlNodeList nodelist = doc.SelectNodes("*/*"); // Select all children of the root node
Assert.IsTrue(nodelist.Count > 0);  // Passes
RemoveChildren(doc.DocumentElement);
Assert.IsTrue(nodelist.Count == 0); // FAILS!?

Почему может быть добавлено простое nodelist.Count перед удалением некоторых узлов вызывает несоответствие в содержимом nodelist после удаления узлов?

Как выясняется, не существует определенного поведения для XmlNodeList, возвращаемого SelectNodes() в этой ситуации. Из замечаний по документации для XmlNode.SelectNodes():

Объект XmlNodeList, возвращаемый этим методом, будет действительным в то время как базовый документ остается без изменений. Если базовый документ изменяется, могут быть возвращены неожиданные результаты (исключение не будет выдано).

Такие "неожиданные результаты" - это то, что вы наблюдаете. После вызова RemoveChildren() содержимое nodelist не указывается и не гарантируется. Net. (На самом деле, похоже, что возвращаемое XmlNodeList использует механизм lazyvaluation . Как только список узлов посчитан или итерирован (но не раньше), запрос XPath оценивается один раз и только один раз. и результаты кэшируются и впоследствии используются повторно.)

Это документированное ограничение на списки узлов, возвращаемых запросами XPath, отличается от общей документации для XmlNodeList, которая гласит:

Изменения в потомках объекта узла, из которого была создана коллекция XmlNodeList, немедленно отражаются в узлах, возвращаемых свойствами и методами XmlNodeList.

Это замечание выглядит как применяется только к XmlNodeList дочерним спискам, возвращаемым методами XmlNode объектов DOM.

Чтобы избежать несоответствия , вы можете явно материализовать XmlNodeList в a List<XmlNode> сразу после оценки, вот так:

var nodelist = doc.SelectNodes("*/*").Cast<XmlNode>().ToList();

Демонстрационная скрипка здесь .

Переключение на LINQ на XML будет другой вариант, так как с ним обычно проще работать, чем с более старым XmlDocument API.

...