1. Introduzione
XML (eXtensible Markup Language) — un linguaggio di markup estensibile, un formato diffuso per memorizzare e scambiare dati strutturati complessi. A differenza di JSON, XML non è particolarmente conciso, ma è ancora molto usato nel settore bancario, nello scambio tra servizi pubblici, nei documenti e nelle integrazioni con 1С, SAP, Oracle ecc. Alcuni dati in Windows e .NET (per esempio i config) sono ancora conservati in formato XML.
Esteticamente XML assomiglia a HTML, solo che non ha un set fisso di tag come <body> o <div> — definisci tu la struttura degli elementi. Esempio:
<Person>
<FirstName>Ivan</FirstName>
<LastName>Petrov</LastName>
<Age>30</Age>
</Person>
Basi di XML e mapping agli oggetti C#
A differenza di JSON, in XML non ci sono tipi speciali per array o booleani — tutto è rappresentato da elementi e attributi. Le strutture a lista si realizzano con elementi ripetuti che hanno lo stesso nome.
Per lavorare comodamente con XML in C# si usa il meccanismo di serializzazione/deserializzazione tramite XmlSerializer.
Serializzazione — la conversione di un oggetto C# in una stringa XML. Deserializzazione — il processo inverso: ottenere un oggetto C# da XML.
Funziona "out of the box" se le tue classi rispettano certe condizioni — le vedremo sotto.
Perché usare XmlSerializer?
XmlSerializer — lo strumento standard di .NET (namespace System.Xml.Serialization) per convertire oggetti in XML e viceversa. È semplice, type-safe e adatto per import/export di dati, config, scambi tra sistemi e integrazioni con servizi pubblici.
2. Esempio: serializzare un oggetto
Supponiamo di avere il modello utente:
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
Serializziamo un oggetto in XML:
using System.Xml.Serialization;
User user = new User { FirstName = "Ivan", LastName = "Petrov", Age = 30 };
// Per la serializzazione serve il tipo
XmlSerializer serializer = new XmlSerializer(typeof(User));
// Scriviamo l'XML su file
using FileStream fs = new FileStream("user.xml", FileMode.Create);
serializer.Serialize(fs, user);
Che succede?
- Crei un'istanza della classe User.
- Crei un'istanza di XmlSerializer per il tipo User.
- Apri il file con FileStream.
- Chiami il metodo Serialize(), che trasforma l'oggetto in XML e lo scrive sul file.
Contenuto del file user.xml:
<?xml version="1.0"?>
<User xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FirstName>Ivan</FirstName>
<LastName>Petrov</LastName>
<Age>30</Age>
</User>
Vedi? Poco codice e l'XML è pronto.
3. Deserializzazione: leggere l'XML e ricreare l'oggetto
Leggiamo l'XML creato e ricostruiamo l'oggetto C#:
// Apriamo il file XML
using FileStream fs = new FileStream("user.xml", FileMode.Open);
User? loadedUser = serializer.Deserialize(fs) as User;
Console.WriteLine($"{loadedUser?.FirstName} {loadedUser?.LastName}, età: {loadedUser?.Age}");
Il metodo Deserialize() restituisce un object, quindi facciamo il cast a User (con as). Se il file è corretto e corrisponde al modello, l'oggetto viene ricostruito completamente.
4. Come funziona la serializzazione: caratteristiche tipiche
La serializzazione in XML richiede il rispetto di certe regole:
• La classe da serializzare deve avere un costruttore pubblico di default (public senza parametri).
• Le proprietà serializzabili devono essere pubbliche: servono sia getter che setter. I membri privati vengono ignorati.
• Non funzionano le proprietà read-only, né le proprietà statiche o astratte.
Se violi le regole riceverai una InvalidOperationException con un messaggio tipo "XmlSerializer cannot serialize this type".
5. Serializzazione di collezioni: array e liste
Se devi serializzare più utenti, usa una classe wrapper:
public class UserList
{
public List<User> Users { get; set; } = new List<User>();
}
Creiamo la collezione e serializziamo:
UserList users = new UserList
{
Users = new List<User>
{
new User { FirstName = "Ivan", LastName = "Petrov", Age = 30 },
new User { FirstName = "Maria", LastName = "Ivanova", Age = 25 }
}
};
XmlSerializer serializer = new XmlSerializer(typeof(UserList));
using FileStream fs = new FileStream("users.xml", FileMode.Create);
serializer.Serialize(fs, users);
Otteniamo l'XML:
<?xml version="1.0"?>
<UserList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Users>
<User>
<FirstName>Ivan</FirstName>
<LastName>Petrov</LastName>
<Age>30</Age>
</User>
<User>
<FirstName>Maria</FirstName>
<LastName>Ivanova</LastName>
<Age>25</Age>
</User>
</Users>
</UserList>
Si può serializzare semplicemente List<User>? Sì, ma l'elemento radice si chiamerà ArrayOfUser, cosa che non sempre va bene per le integrazioni.
6. Attributi per controllare la serializzazione
Con gli attributi puoi controllare il formato XML: rinominare elementi, serializzare come attributi, ignorare campi, ecc. Attributi utili: [XmlElement], [XmlAttribute], [XmlArray], [XmlIgnore].
Serializzare come attributi
Scriviamo nome e cognome come attributi e l'età come elemento:
public class User
{
[XmlAttribute]
public string FirstName { get; set; }
[XmlAttribute]
public string LastName { get; set; }
[XmlElement]
public int Age { get; set; }
}
Risultato:
<User FirstName="Ivan" LastName="Petrov">
<Age>30</Age>
</User>
Controllo dei nomi degli elementi
Rinominiamo i tag in modo più leggibile:
public class User
{
[XmlElement("Nome")]
public string FirstName { get; set; }
[XmlElement("Cognome")]
public string LastName { get; set; }
[XmlElement("Età")]
public int Age { get; set; }
}
XML risultante:
<User>
<Nome>Ivan</Nome>
<Cognome>Petrov</Cognome>
<Età>30</Età>
</User>
Ignorare proprietà
Se una proprietà non deve essere serializzata, marcala con [XmlIgnore]:
public class User
{
public string FirstName { get; set; }
[XmlIgnore]
public string InternalCode { get; set; }
}
7. Serializzazione di gerarchie e oggetti annidati
Le classi annidate si trasformano facilmente in elementi XML annidati:
public class Address
{
public string City { get; set; }
public string Street { get; set; }
}
public class User
{
public string FirstName { get; set; }
public Address Address { get; set; }
}
var user = new User
{
FirstName = "Anna",
Address = new Address { City = "Neongrad", Street = "Vyazovaya" }
};
XmlSerializer serializer = new XmlSerializer(typeof(User));
using var fs = new FileStream("user_with_address.xml", FileMode.Create);
serializer.Serialize(fs, user);
XML:
<User>
<FirstName>Anna</FirstName>
<Address>
<City>Neongrad</City>
<Street>Vyazovaya</Street>
</Address>
</User>
8. Serializzazione da/a stringa (senza file)
Serializzare in una stringa
using System.Text;
var serializer = new XmlSerializer(typeof(User));
using var stringWriter = new StringWriter();
serializer.Serialize(stringWriter, user);
string xml = stringWriter.ToString();
Console.WriteLine(xml);
Deserializzare da una stringa
var xml = "<User><FirstName>Anna</FirstName></User>";
using var stringReader = new StringReader(xml);
User user = (User)serializer.Deserialize(stringReader);
Console.WriteLine(user.FirstName);
9. Esempio: export e import dei dati dell'app
Export/import della lista utenti su file XML:
public static void ExportUsers(UserList users, string path)
{
var serializer = new XmlSerializer(typeof(UserList));
using var fs = new FileStream(path, FileMode.Create);
serializer.Serialize(fs, users);
}
public static UserList ImportUsers(string path)
{
var serializer = new XmlSerializer(typeof(UserList));
using var fs = new FileStream(path, FileMode.Open);
return (UserList)serializer.Deserialize(fs);
}
Uso: backup, scambio con altri servizi, integrazioni. Ai colloqui le domande su XmlSerializer capitano più spesso di quanto pensi — specialmente in aziende che lavorano con servizi pubblici e banche.
10. Errori tipici e caratteristiche del funzionamento
Se un campo non è public, non viene serializzato.
Le proprietà read-only vengono ignorate.
Proprietà di tipo interfaccia o di classe astratta non sono gestite dal serializer.
Il tipo DateTime viene serializzato nel formato XSD (vedi la documentazione).
Collezioni come array e List<T> funzionano in modo prevedibile, mentre con ObservableCollection<T> possono esserci sorprese per sistemi esterni.
Dettagli sulle codifiche: se scrivi con StreamWriter senza specificare la codifica, potresti ottenere UTF-16; per compatibilità spesso ci si aspetta UTF-8 — imposta la codifica esplicitamente.
GO TO FULL VERSION