1. Introdução
O aparecimento do LINQ foi uma revolução no trabalho com XML no .NET. Antes, para modificar XML tinha que-se escrever construções pesadas com NodeList e XmlElement, agora podemos expressar todas as operações no estilo LINQ. É como trocar uma estrada de terra cheia de curvas por uma rodovia rápida.
LINQ to XML resolve três problemas principais:
- Simplifica a sintaxe de construir e ler XML.
- Torna a navegação pelo documento semelhante ao trabalho com coleções.
- Permite integrar XML com qualquer consulta LINQ.
Classes principais: XDocument, XElement, XAttribute
Antes de mergulhar nas águas profundas do LINQ to XML, vamos conhecer três pilares:
- XDocument — a raiz de todo o documento, tipo o lombinho do livro que contém tudo.
- XElement — qualquer nó (elemento) XML. É como uma caixa no depósito que pode conter outras caixas ou produtos (elementos, atributos e texto).
- XAttribute — o atributo de um elemento, por exemplo, <User id="42" />.
Esquema visual
+-----------------+
| XDocument |---------------------+
+-----------------+ |
| (Root XElement)
v |
+-------------+ +-----------------+
| XElement |<-------------| XElement (root) |
+-------------+ +-----------------+
| |
(XAttribute) (nested XElements)
2. Lendo XML com LINQ to XML
Vamos supor que no nosso app educacional (por exemplo, uma agenda) queremos ler contatos a partir de um arquivo XML. Que a estrutura do arquivo seja assim:
<Contacts>
<Person id="1">
<Name>Ivan Ivanov</Name>
<Email>ivan@example.com</Email>
<Phones>
<Phone type="mobile">+12 999 123-45-67</Phone>
<Phone type="home">+12 812 123-45-67</Phone>
</Phones>
</Person>
<Person id="2">
<Name>Maria Petrova</Name>
<Email>maria@example.com</Email>
<Phones>
<Phone type="mobile">+12 921 123-45-67</Phone>
</Phones>
</Person>
</Contacts>
Lendo o XML para a memória
Primeiro adicionamos a diretiva no projeto:
using System.Xml.Linq;
Agora lemos o arquivo ou a string:
// Suponha que xmlString contenha o documento XML, ou use XDocument.Load("contacts.xml")
var doc = XDocument.Parse(xmlString);
// Pegamos a lista de Person
var persons = doc.Root.Elements("Person");
foreach (var person in persons)
{
string name = person.Element("Name")?.Value ?? "Sem nome";
string email = person.Element("Email")?.Value ?? "Sem e-mail";
string id = person.Attribute("id")?.Value ?? "sem id";
Console.WriteLine($"Nome: {name}, Email: {email}, id: {id}");
}
- doc.Root dá o elemento raiz (Contacts).
- Elements("Person") retorna todos os <Person>.
- Depois pegamos elementos/atributos necessários via .Element("...") ou .Attribute("...").
- O operador ?. protege contra erros se o elemento/atributo não for encontrado.
Analogia
Trabalhar com XElement é como desmontar uma matrioska: você tira uma, dentro tem outra, e assim por diante. Só que agora isso é feito com uma consulta simples, sem percorridas pesadas.
3. Montando consultas LINQ sobre XML
Quer obter a lista de todos os telefones móveis de todas as pessoas? Fácil, em estilo LINQ:
var mobilePhones = doc
.Descendants("Phone") // procura todos os Phone em qualquer nível
.Where(p => (string)p.Attribute("type") == "mobile")
.Select(p => p.Value);
foreach (string phone in mobilePhones)
{
Console.WriteLine($"Móvel: {phone}");
}
- Descendants("Phone") busca todas as tags <Phone> no documento.
- Attribute("type") — obtém o atributo.
- Conversão (string) no atributo retorna automaticamente a string ou null.
Truques legais
Você pode buscar imediatamente através de vários níveis sem se preocupar com a complexidade da navegação. Isso seria bem mais chato com XmlDocument.
4. Criando e modificando XML "on the fly"
É confortável criar XML em memória usando os construtores XElement e XDocument. Exemplo de adicionar uma nova pessoa ao documento (parte do nosso "app-agenda" em evolução):
// Criamos um novo Person
var newPerson = new XElement("Person",
new XAttribute("id", "3"),
new XElement("Name", "Sergey Novikov"),
new XElement("Email", "sergey@example.com"),
new XElement("Phones",
new XElement("Phone", new XAttribute("type", "work"), "+12 812 987-65-43")
)
);
// Adicionamos à raiz
doc.Root.Add(newPerson);
// Salvamos de volta no arquivo (ou em string)
doc.Save("contacts.xml");
Comentários:
- Dá para aninhar elementos usando construtores "matrioska": o primeiro argumento é a tag, os demais são o conteúdo aninhado (podem ser elementos, atributos, texto).
- Atributos normalmente vêm antes do conteúdo direto (ex.: <Phone type="work">...).
5. Interação com coleções C# — a mágica do LINQ
Frequentemente precisamos serializar uma coleção comum de objetos (por exemplo, List<Person>) para XML ou o inverso. Isso é bem simples, quase uma linha de LINQ:
var people = new List<Person>
{
new Person { Id = 1, Name = "Ivan Ivanov", Email = "ivan@example.com" },
// etc.
};
var doc = new XDocument(
new XElement("Contacts",
people.Select(p =>
new XElement("Person",
new XAttribute("id", p.Id),
new XElement("Name", p.Name),
new XElement("Email", p.Email)
)
)
)
);
A classe Person é assim (para contexto):
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
Agora você tem uma conversão simples, clara e controlada de uma coleção C# para XML!
6. Métodos úteis e dicas de XElement e XDocument
Navegação
- .Elements("TagName") — todos os elementos imediatos (filhos diretos).
- .Descendants("TagName") — todos os elementos aninhados em qualquer lugar dentro do elemento atual.
- .Parent — elemento pai.
- .Ancestors() — todos os ancestrais do elemento (útil para achar o caminho).
- .FirstNode, .LastNode, .NextNode, .PreviousNode — navegação entre nós vizinhos.
Busca com condições
var personsWithEmail = doc.Root
.Elements("Person")
.Where(p => !string.IsNullOrEmpty((string)p.Element("Email")));
Adicionar e remover
// Adicionar telefone a um usuário existente
var maria = doc.Root.Elements("Person")
.FirstOrDefault(p => (string)p.Element("Name") == "Maria Petrova");
maria?.Element("Phones")?.Add(
new XElement("Phone", new XAttribute("type", "work"), "+12 812 111-22-33")
);
// Remover usuário por id
doc.Root.Elements("Person")
.Where(p => (string)p.Attribute("id") == "3")
.Remove();
Converter de volta para string/arquivo
string xmlText = doc.ToString(); // com formatação
doc.Save("contacts.xml");
7. Nuances úteis
Comparando LINQ to XML com outras abordagens (.NET)
| Abordagem | Quando usar | Vantagens | Desvantagens |
|---|---|---|---|
|
Código legado, antigo | Integração com XPath | Muito código, API verbosa |
|
Para serializar objetos | Simples serializar C# ↔ XML | Baixa flexibilidade |
|
Quase tudo: parsing, edição | Linear, conciso, poderoso, LINQ | Usa um pouco mais de memória |
Aplicação em projetos reais e em entrevistas
- Analisar arquivos de configuração onde não existe um mapeamento claro para classes C#.
- Importação/exportação de dados (por exemplo, exportar/importar dados de usuário).
- Migrações, tratamento de XML "sujo" ou complexo.
- Transformações rápidas (por exemplo, converter um formato XML em outro).
"Por que eu devo saber isso?" — em entrevistas podem pedir para transformar ou parsear um XML complexo; lembrando LINQ to XML, você fará isso de forma mais simples e elegante do que usando APIs antigas.
8. Erros típicos ao trabalhar com LINQ to XML
Erro nº1: tentar acessar um elemento ausente sem checagem.
Se o elemento pode estar ausente, use o operador ?. ou verifique null antes. Caso contrário, você vai receber NullReferenceException e um crash inesperado em tempo de execução.
Erro nº2: confundir atributos com elementos.
Atributos e elementos são coisas diferentes em XML. Às vezes os dados podem estar em um ou em outro. Por exemplo:
<User login="admin"><Status>active</Status></User>
Para obter o valor do atributo login, use .Attribute("login"), e para obter o conteúdo da tag <Status> use .Element("Status").
Erro nº3: não entender o comportamento do método .Remove().
O método .Remove() modifica imediatamente a estrutura do documento XML em memória. Ele não retorna nada, e "desfazer" a remoção não é possível. Iniciantes às vezes esperam que ele retorne os elementos removidos ou que de alguma forma sinalize — isso não acontece.
GO TO FULL VERSION