Keywords: C# | XML Serialization | Generic Lists | Polymorphic Serialization | XmlSerializer
Abstract: This article provides an in-depth analysis of the technical challenges encountered when serializing generic lists containing multiple types of objects in C#. It examines the type limitations of XmlSerializer and presents comprehensive solutions using XmlInclude attributes and the XmlSerializer(Type, Type[]) constructor. The article includes complete code examples demonstrating serialization of polymorphic object hierarchies, from simple types to complex inheritance structures, along with fundamental principles and best practices for XML serialization.
Problem Background and Challenges
In C# development, there is often a need to serialize generic lists containing multiple types of objects into XML format. Many developers initially attempt to use List<ISerializable> or List<object> for this purpose, but encounter serialization errors. The core issue is that XmlSerializer cannot directly handle interface types or unknown concrete types.
When attempting to serialize List<ISerializable>, the system throws "Cannot serialize interface System.Runtime.Serialization.ISerializable" exception. Using List<object> produces "There was an error generating the XML document" error, with inner exception indicating that specific types cannot be used in the current context.
Core Principles of the Solution
XmlSerializer requires explicit knowledge of all possible concrete types during serialization. For object collections containing inheritance relationships, all derived types must be pre-declared either through XmlInclude attributes or explicitly specified in the XmlSerializer constructor using a type array.
The following example demonstrates the complete solution architecture:
[XmlRoot("PersonenListe")]
[XmlInclude(typeof(Person))]
public class PersonalList
{
[XmlArray("PersonenArray")]
[XmlArrayItem("PersonObjekt")]
public List<Person> Persons = new List<Person>();
[XmlElement("Listname")]
public string Listname { get; set; }
public PersonalList() { }
public PersonalList(string name)
{
this.Listname = name;
}
public void AddPerson(Person person)
{
Persons.Add(person);
}
}Base Type Definition and Inheritance Structure
Define the base Person class as the foundation for all personnel types:
[XmlType("Person")]
[XmlInclude(typeof(SpecialPerson)), XmlInclude(typeof(SuperPerson))]
public class Person
{
[XmlAttribute("PersID", DataType = "string")]
public string ID { get; set; }
[XmlElement("Name")]
public string Name { get; set; }
[XmlElement("City")]
public string City { get; set; }
[XmlElement("Age")]
public int Age { get; set; }
public Person() { }
public Person(string name, string city, int age, string id)
{
this.Name = name;
this.City = city;
this.Age = age;
this.ID = id;
}
}Create SpecialPerson derived class with additional interests property:
[XmlType("SpecialPerson")]
public class SpecialPerson : Person
{
[XmlElement("SpecialInterests")]
public string Interests { get; set; }
public SpecialPerson() { }
public SpecialPerson(string name, string city, int age, string id, string interests)
{
this.Name = name;
this.City = city;
this.Age = age;
this.ID = id;
this.Interests = interests;
}
}Define SuperPerson class containing skills list and alias:
[XmlType("SuperPerson")]
public class SuperPerson : Person
{
[XmlArray("Skills")]
[XmlArrayItem("Skill")]
public List<String> Skills { get; set; }
[XmlElement("Alias")]
public string Alias { get; set; }
public SuperPerson()
{
Skills = new List<String>();
}
public SuperPerson(string name, string city, int age, string id, string[] skills, string alias)
{
Skills = new List<String>();
this.Name = name;
this.City = city;
this.Age = age;
this.ID = id;
foreach (string item in skills)
{
this.Skills.Add(item);
}
this.Alias = alias;
}
}Complete Serialization and Deserialization Implementation
The following code demonstrates the complete serialization and deserialization workflow:
static void Main(string[] args)
{
PersonalList personen = new PersonalList();
personen.Listname = "Friends";
// Create normal person instance
Person normPerson = new Person();
normPerson.ID = "0";
normPerson.Name = "Max Man";
normPerson.City = "Capitol City";
normPerson.Age = 33;
// Create special person instance
SpecialPerson specPerson = new SpecialPerson();
specPerson.ID = "1";
specPerson.Name = "Albert Einstein";
specPerson.City = "Ulm";
specPerson.Age = 36;
specPerson.Interests = "Physics";
// Create super person instance
SuperPerson supPerson = new SuperPerson();
supPerson.ID = "2";
supPerson.Name = "Superman";
supPerson.Alias = "Clark Kent";
supPerson.City = "Metropolis";
supPerson.Age = int.MaxValue;
supPerson.Skills.Add("fly");
supPerson.Skills.Add("strong");
// Add all persons to the list
personen.AddPerson(normPerson);
personen.AddPerson(specPerson);
personen.AddPerson(supPerson);
// Serialization process
Type[] personTypes = { typeof(Person), typeof(SpecialPerson), typeof(SuperPerson) };
XmlSerializer serializer = new XmlSerializer(typeof(PersonalList), personTypes);
FileStream fs = new FileStream("Personenliste.xml", FileMode.Create);
serializer.Serialize(fs, personen);
fs.Close();
personen = null;
// Deserialization process
fs = new FileStream("Personenliste.xml", FileMode.Open);
personen = (PersonalList)serializer.Deserialize(fs);
serializer.Serialize(Console.Out, personen);
Console.ReadLine();
}Technical Points and Best Practices
When implementing XML serialization of generic lists, several key points require attention: All serializable classes must include parameterless constructors, which is a fundamental requirement of XmlSerializer. For type hierarchies containing inheritance relationships, all possible derived types must be explicitly declared either in the base class or in the serializer constructor.
Using XmlArray and XmlArrayItem attributes allows precise control over how collections are represented in XML. Explicitly specifying type arrays through the XmlSerializer(Type, Type[]) constructor avoids runtime type discovery issues.
In practical projects, it is recommended to centralize type declarations for easier maintenance and extension. For complex serialization requirements, consider implementing the IXmlSerializable interface for finer control, though this increases code complexity.
Common Issues and Solutions
Developers frequently encounter issues when attempting to serialize interface types or undeclared derived types. The solution is to ensure all actually serialized types are properly declared. Another common mistake is neglecting the parameterless constructor requirement, which causes serialization failures.
For performance-sensitive scenarios, consider caching XmlSerializer instances since creating XmlSerializer involves significant overhead. Additionally, note that XML serialization does not support circular references, which should be avoided in data structure design.
Through the solutions presented in this article, developers can effectively address XML serialization of generic lists of serializable objects, achieving flexible and reliable data persistence solutions.