Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
LINQ to XML contém vários métodos que permitem modificar uma árvore XML diretamente. Você pode adicionar elementos, excluir elementos, alterar o conteúdo de um elemento, adicionar atributos e assim por diante. Essa interface de programação é descrita em Modificar árvores XML. Se você estiver iterando em um dos eixos, como Elements, e estiver modificando a árvore XML à medida que itera no eixo, você pode acabar com alguns bugs estranhos.
Às vezes, esse problema é conhecido como "O Problema do Halloween".
Quando você escreve algum código usando LINQ que itera por meio de uma coleção, você está escrevendo código em um estilo declarativo. É mais parecido com descrever o que você quer, em vez de como você quer fazê-lo. Se você escrever o código que 1) obtém o primeiro elemento, 2) testa-o para alguma condição, 3) modifica-o e 4) coloca-o de volta na lista, então esse código seria imperativo. Você está dizendo ao computador como fazer o que você quer fazer.
Misturar esses estilos de código na mesma operação é o que leva a problemas. Considere o seguinte:
Suponha que você tenha uma lista vinculada com três itens nela (a, b e c):
a -> b -> c
Agora, suponha que você queira passar pela lista vinculada, adicionando três novos itens (a', b' e c'). Você deseja que a lista vinculada resultante seja semelhante a esta:
a -> a' -> b -> b' -> c -> c'
Portanto, você escreve um código que itera por meio da lista e, para cada item, adiciona um novo item logo após ele. O que acontece é que seu código verá primeiro o a elemento e inserirá a' depois dele. Agora, seu código passará para o próximo nó da lista, que é agora a', então ele adiciona um novo item entre a' e b à lista!
Como você resolveria isso? Bem, você pode fazer uma cópia da lista vinculada original e criar uma lista completamente nova. Ou se você estiver escrevendo código puramente imperativo, poderá encontrar o primeiro item, adicionar o novo item e avançar duas vezes na lista vinculada, avançando sobre o elemento que acabou de adicionar.
Exemplo: Adicionar durante a iteração
Por exemplo, suponha que você queira escrever código para criar uma duplicata de cada elemento em uma árvore:
XElement root = new XElement("Root",
new XElement("A", "1"),
new XElement("B", "2"),
new XElement("C", "3")
);
foreach (XElement e in root.Elements())
root.Add(new XElement(e.Name, (string)e));
Dim root As XElement = _
<Root>
<A>1</A>
<B>2</B>
<C>3</C>
</Root>
For Each e As XElement In root.Elements()
root.Add(New XElement(e.Name, e.Value))
Next
Esse código entra em um loop infinito. A instrução foreach itera através do eixo Elements(), adicionando novos elementos ao elemento doc. Acaba também iterar através dos elementos que acabou de adicionar. E como aloca novos objetos a cada iteração do loop, ele eventualmente consumirá toda a memória disponível.
Você pode corrigir esse problema puxando a coleção para a memória usando o ToList operador de consulta padrão, da seguinte maneira:
XElement root = new XElement("Root",
new XElement("A", "1"),
new XElement("B", "2"),
new XElement("C", "3")
);
foreach (XElement e in root.Elements().ToList())
root.Add(new XElement(e.Name, (string)e));
Console.WriteLine(root);
Dim root As XElement = _
<Root>
<A>1</A>
<B>2</B>
<C>3</C>
</Root>
For Each e As XElement In root.Elements().ToList()
root.Add(New XElement(e.Name, e.Value))
Next
Console.WriteLine(root)
Agora o código funciona. A árvore XML resultante é a seguinte:
<Root>
<A>1</A>
<B>2</B>
<C>3</C>
<A>1</A>
<B>2</B>
<C>3</C>
</Root>
Exemplo: Excluir durante a iteração
Se você deseja excluir todos os nós em um determinado nível, você pode ter tentado escrever código como o seguinte:
XElement root = new XElement("Root",
new XElement("A", "1"),
new XElement("B", "2"),
new XElement("C", "3")
);
foreach (XElement e in root.Elements())
e.Remove();
Console.WriteLine(root);
Dim root As XElement = _
<Root>
<A>1</A>
<B>2</B>
<C>3</C>
</Root>
For Each e As XElement In root.Elements()
e.Remove()
Next
Console.WriteLine(root)
No entanto, isso não faz o que você quer. Nessa situação, depois de remover o primeiro elemento, A, ele será removido da árvore XML contida na raiz e o código no método Elements que faz a iteração não poderá encontrar o próximo elemento.
Este exemplo produz a seguinte saída:
<Root>
<B>2</B>
<C>3</C>
</Root>
A solução novamente é chamar ToList para materializar a coleção, da seguinte maneira:
XElement root = new XElement("Root",
new XElement("A", "1"),
new XElement("B", "2"),
new XElement("C", "3")
);
foreach (XElement e in root.Elements().ToList())
e.Remove();
Console.WriteLine(root);
Dim root As XElement = _
<Root>
<A>1</A>
<B>2</B>
<C>3</C>
</Root>
For Each e As XElement In root.Elements().ToList()
e.Remove()
Next
Console.WriteLine(root)
Este exemplo produz a seguinte saída:
<Root />
Como alternativa, você pode eliminar completamente a iteração chamando RemoveAll o elemento pai:
XElement root = new XElement("Root",
new XElement("A", "1"),
new XElement("B", "2"),
new XElement("C", "3")
);
root.RemoveAll();
Console.WriteLine(root);
Dim root As XElement = _
<Root>
<A>1</A>
<B>2</B>
<C>3</C>
</Root>
root.RemoveAll()
Console.WriteLine(root)
Exemplo: por que o LINQ não pode lidar automaticamente com esses problemas
Uma abordagem seria sempre trazer tudo na memória em vez de fazer a avaliação lazy. No entanto, seria muito caro em termos de desempenho e uso de memória. Na verdade, se LINQ e LINQ to XML adotarem essa abordagem, isso falhará em situações reais.
Outra abordagem possível seria colocar algum tipo de sintaxe de transação no LINQ e fazer com que o compilador tente analisar o código para determinar se alguma coleção específica precisava ser materializada. No entanto, tentar determinar todo o código que tem efeitos colaterais é incrivelmente complexo. Considere o seguinte código:
var z =
from e in root.Elements()
where TestSomeCondition(e)
select DoMyProjection(e);
Dim z = _
From e In root.Elements() _
Where (TestSomeCondition(e)) _
Select DoMyProjection(e)
Esse código de análise precisaria analisar os métodos TestSomeCondition e DoMyProjection e todos os métodos chamados por esses métodos para determinar se algum código tinha efeitos colaterais. Mas o código de análise não podia apenas procurar qualquer código que tivesse efeitos colaterais. Precisaria para selecionar apenas o código que tinha efeitos colaterais em elementos filho de root nesta situação.
LINQ to XML não tenta fazer nenhuma análise desse tipo. Cabe a você evitar esses problemas.
Exemplo: usar código declarativo para gerar uma nova árvore XML em vez de modificar a árvore existente
Para evitar esses problemas, não misture código declarativo e imperativo, mesmo se você souber exatamente a semântica de suas coleções e a semântica dos métodos que modificam a árvore XML. Se você escrever um código que evite problemas, seu código precisará ser mantido por outros desenvolvedores no futuro e eles podem não ser tão claros sobre os problemas. Se você misturar estilos de codificação declarativos e imperativos, seu código será mais frágil. Se você escrever um código que materialize uma coleção para que esses problemas sejam evitados, adicione comentários conforme necessário em seu código, para que os programadores de manutenção entendam a questão.
Se o desempenho e outras considerações permitirem, use apenas o código declarativo. Não modifique sua árvore XML existente. Em vez disso, gere um novo, conforme mostrado no exemplo a seguir:
XElement root = new XElement("Root",
new XElement("A", "1"),
new XElement("B", "2"),
new XElement("C", "3")
);
XElement newRoot = new XElement("Root",
root.Elements(),
root.Elements()
);
Console.WriteLine(newRoot);
Dim root As XElement = _
<Root>
<A>1</A>
<B>2</B>
<C>3</C>
</Root>
Dim newRoot As XElement = New XElement("Root", _
root.Elements(), root.Elements())
Console.WriteLine(newRoot)