XML Serialize generic list of serializable objects

Can I serialize a generic list of serializable objects without having to specify their type.

Something like the intention behind the broken code below:

List<ISerializable> serializableList = new List<ISerializable>();


XmlSerializer xmlSerializer = new XmlSerializer(serializableList.GetType());


serializableList.Add((ISerializable)PersonList);


using (StreamWriter streamWriter = System.IO.File.CreateText(fileName))
{
xmlSerializer.Serialize(streamWriter, serializableList);
}

Edit:

For those who wanted to know detail: when I try to run this code, it errors on the XMLSerializer[...] line with:

Cannot serialize interface System.Runtime.Serialization.ISerializable.

If I change to List<object> I get "There was an error generating the XML document.". The InnerException detail is "{"The type System.Collections.Generic.List1[[Project1.Person, ConsoleFramework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] may not be used in this context."}"

The person object is defined as follows:

[XmlRoot("Person")]
public class Person
{
string _firstName = String.Empty;
string _lastName = String.Empty;


private Person()
{
}


public Person(string lastName, string firstName)
{
_lastName = lastName;
_firstName = firstName;
}


[XmlAttribute(DataType = "string", AttributeName = "LastName")]
public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}


[XmlAttribute(DataType = "string", AttributeName = "FirstName")]
public string FirstName
{
get { return _firstName; }
set { _firstName = value; }
}
}

The PersonList is just a List<Person> .

This is just for testing though, so didn't feel the details were too important. The key is I have one or more different objects, all of which are serializable. I want to serialize them all to one file. I thought the easiest way to do that would be to put them in a generic list and serialize the list in one go. But this doesn't work.

I tried with List<IXmlSerializable> as well, but that fails with

System.Xml.Serialization.IXmlSerializable cannot be serialized because it does not have a parameterless constructor.

Sorry for the lack of detail, but I am a beginner at this and don't know what detail is required. It would be helpful if people asking for more detail tried to respond in a way that would leave me understanding what details are required, or a basic answer outlining possible directions.

Also thanks to the two answers I've got so far - I could have spent a lot more time reading without getting these ideas. It's amazing how helpful people are on this site.

238045 次浏览

I think it's best if you use methods with generic arguments, like the following :

public static void SerializeToXml<T>(T obj, string fileName)
{
using (var fileStream = new FileStream(fileName, FileMode.Create))
{
var ser = new XmlSerializer(typeof(T));
ser.Serialize(fileStream, obj);
}
}


public static T DeserializeFromXml<T>(string xml)
{
T result;
var ser = new XmlSerializer(typeof(T));
using (var tr = new StringReader(xml))
{
result = (T)ser.Deserialize(tr);
}
return result;
}

I think Dreas' approach is ok. An alternative to this however is to have some static helper methods and implement IXmlSerializable on each of your methods e.g an XmlWriter extension method and the XmlReader one to read it back.

public static void SaveXmlSerialiableElement<T>(this XmlWriter writer, String elementName, T element) where T : IXmlSerializable
{
writer.WriteStartElement(elementName);
writer.WriteAttributeString("TYPE", element.GetType().AssemblyQualifiedName);
element.WriteXml(writer);
writer.WriteEndElement();
}


public static T ReadXmlSerializableElement<T>(this XmlReader reader, String elementName) where T : IXmlSerializable
{
reader.ReadToElement(elementName);


Type elementType = Type.GetType(reader.GetAttribute("TYPE"));
T element = (T)Activator.CreateInstance(elementType);
element.ReadXml(reader);
return element;
}

If you do go down the route of using the XmlSerializer class directly, create serialization assemblies before hand if possible, as you can take a large performance hit in constructing new XmlSerializers regularly.

For a collection you need something like this:

public static void SaveXmlSerialiazbleCollection<T>(this XmlWriter writer, String collectionName, String elementName, IEnumerable<T> items) where T : IXmlSerializable
{
writer.WriteStartElement(collectionName);
foreach (T item in items)
{
writer.WriteStartElement(elementName);
writer.WriteAttributeString("TYPE", item.GetType().AssemblyQualifiedName);
item.WriteXml(writer);
writer.WriteEndElement();
}
writer.WriteEndElement();
}

See 介绍XML序列化:

Items That Can Be Serialized

The following items can be serialized using the XmlSerializer class:

  • Public read/write properties and fields of public classes
  • Classes that implement ICollection or IEnumerable
  • XmlElement objects
  • XmlNode objects
  • DataSet objects

In particular, ISerializable or the [Serializable] attribute does not matter.


Now that you've told us what your problem is ("it doesn't work" is not a problem statement), you can get answers to your actual problem, instead of guesses.

When you serialize a collection of a type, but will actually be serializing a collection of instances of derived types, you need to let the serializer know which types you will actually be serializing. This is also true for collections of object.

You need to use the XmlSerializer(Type,Type[]) constructor to give the list of possible types.

If the XML output requirement can be changed you can always use binary serialization - which is better suited for working with heterogeneous lists of objects. Here's an example:

private void SerializeList(List<Object> Targets, string TargetPath)
{
IFormatter Formatter = new BinaryFormatter();


using (FileStream OutputStream = System.IO.File.Create(TargetPath))
{
try
{
Formatter.Serialize(OutputStream, Targets);
} catch (SerializationException ex) {
//(Likely Failed to Mark Type as Serializable)
//...
}
}

Use as such:

[Serializable]
public class Animal
{
public string Home { get; set; }
}


[Serializable]
public class Person
{
public string Name { get; set; }
}




public void ExampleUsage() {


List<Object> SerializeMeBaby = new List<Object> {
new Animal { Home = "London, UK" },
new Person { Name = "Skittles" }
};


string TargetPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"Test1.dat");


SerializeList(SerializeMeBaby, TargetPath);
}

You can't serialize a collection of objects without specifying the expected types. You must pass the list of expected types to the constructor of XmlSerializer (the extraTypes parameter) :

List<object> list = new List<object>();
list.Add(new Foo());
list.Add(new Bar());


XmlSerializer xs = new XmlSerializer(typeof(object), new Type[] {typeof(Foo), typeof(Bar)});
using (StreamWriter streamWriter = System.IO.File.CreateText(fileName))
{
xs.Serialize(streamWriter, list);
}

If all the objects of your list inherit from the same class, you can also use the XmlInclude attribute to specify the expected types :

[XmlInclude(typeof(Foo)), XmlInclude(typeof(Bar))]
public class MyBaseClass
{
}

Below is a Util class in my project:

namespace Utils
{
public static class SerializeUtil
{
public static void SerializeToFormatter<F>(object obj, string path) where F : IFormatter, new()
{
if (obj == null)
{
throw new NullReferenceException("obj Cannot be Null.");
}


if (obj.GetType().IsSerializable == false)
{
//  throw new
}
IFormatter f = new F();
SerializeToFormatter(obj, path, f);
}


public static T DeserializeFromFormatter<T, F>(string path) where F : IFormatter, new()
{
T t;
IFormatter f = new F();
using (FileStream fs = File.OpenRead(path))
{
t = (T)f.Deserialize(fs);
}
return t;
}


public static void SerializeToXML<T>(string path, object obj)
{
XmlSerializer xs = new XmlSerializer(typeof(T));
using (FileStream fs = File.Create(path))
{
xs.Serialize(fs, obj);
}
}


public static T DeserializeFromXML<T>(string path)
{
XmlSerializer xs = new XmlSerializer(typeof(T));
using (FileStream fs = File.OpenRead(path))
{
return (T)xs.Deserialize(fs);
}
}


public static T DeserializeFromXml<T>(string xml)
{
T result;


var ser = new XmlSerializer(typeof(T));
using (var tr = new StringReader(xml))
{
result = (T)ser.Deserialize(tr);
}
return result;
}




private static void SerializeToFormatter(object obj, string path, IFormatter formatter)
{
using (FileStream fs = File.Create(path))
{
formatter.Serialize(fs, obj);
}
}
}
}

I have an solution for a generic List<> with dynamic binded items.

class PersonalList it's the root element

[XmlRoot("PersonenListe")]
[XmlInclude(typeof(Person))] // include type class Person
public class PersonalList
{
[XmlArray("PersonenArray")]
[XmlArrayItem("PersonObjekt")]
public List<Person> Persons = new List<Person>();


[XmlElement("Listname")]
public string Listname { get; set; }


// Konstruktoren
public PersonalList() { }


public PersonalList(string name)
{
this.Listname = name;
}


public void AddPerson(Person person)
{
Persons.Add(person);
}
}

class Person it's an single list element

[XmlType("Person")] // define Type
[XmlInclude(typeof(SpecialPerson)), XmlInclude(typeof(SuperPerson))]
// include type class SpecialPerson and class 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; }


// Konstruktoren
public Person() { }


public Person(string name, string city, int age, string id)
{
this.Name = name;
this.City = city;
this.Age = age;
this.ID = id;
}
}

class SpecialPerson inherits Person

[XmlType("SpecialPerson")] // define Type
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;
}
}

class SuperPerson inherits Person

[XmlType("SuperPerson")] // define Type
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;
}
}

and the main test Source

static void Main(string[] args)
{
PersonalList personen = new PersonalList();
personen.Listname = "Friends";


// normal person
Person normPerson = new Person();
normPerson.ID = "0";
normPerson.Name = "Max Man";
normPerson.City = "Capitol City";
normPerson.Age = 33;


// special person
SpecialPerson specPerson = new SpecialPerson();
specPerson.ID = "1";
specPerson.Name = "Albert Einstein";
specPerson.City = "Ulm";
specPerson.Age = 36;
specPerson.Interests = "Physics";


// super person
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 Persons
personen.AddPerson(normPerson);
personen.AddPerson(specPerson);
personen.AddPerson(supPerson);


// Serialize
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;


// Deserialize
fs = new FileStream("Personenliste.xml", FileMode.Open);
personen = (PersonalList)serializer.Deserialize(fs);
serializer.Serialize(Console.Out, personen);
Console.ReadLine();
}

Important is the definition and includes of the diffrent types.

knowTypeList parameter let serialize with DataContractSerializer several known types:

private static void WriteObject(
string fileName, IEnumerable<Vehichle> reflectedInstances, List<Type> knownTypeList)
{
using (FileStream writer = new FileStream(fileName, FileMode.Append))
{
foreach (var item in reflectedInstances)
{
var serializer = new DataContractSerializer(typeof(Vehichle), knownTypeList);
serializer.WriteObject(writer, item);
}
}
}

The easiest way to do it, that I have found.. Apply the System.Xml.Serialization.XmlArray attribute to it.

[System.Xml.Serialization.XmlArray] //This is the part that makes it work
List<object> serializableList = new List<object>();


XmlSerializer xmlSerializer = new XmlSerializer(serializableList.GetType());


serializableList.Add(PersonList);


using (StreamWriter streamWriter = System.IO.File.CreateText(fileName))
{
xmlSerializer.Serialize(streamWriter, serializableList);
}

The serializer will pick up on it being an array and serialize the list's items as child nodes.

Let Serialize and Deserialize be nameless object lists in C#:

// create a list of Shapes to serialize.
List<Shape> listOfShapes = new()
{
new Circle { Colour = "Red", Radius = 2.5 },
new Rectangle { Colour = "Blue", Height = 20.0, Width = 10.0 },
new Circle { Colour = "Green", Radius = 8.0 },
new Circle { Colour = "Purple", Radius = 12.3 },
new Rectangle { Colour = "Blue", Height = 45.0, Width = 18.0 }
};
    

string filepath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "Testfile.xml");
    

//Serialize
List<Type> types = new();
foreach(Shape s in listOfShapes)
{
types.Add(s.GetType());
}
XmlSerializer serializerXml = new XmlSerializer(listOfShapes.GetType(),types.ToArray());
FileStream fsxml = File.Create(filepath);
serializerXml.Serialize(fsxml, listOfShapes);
fsxml.Close();
    

//Deserialize.
FileStream fsxmlr= File.Open(filepath, FileMode.Open);
List<Shape> loadedShapesXml = serializerXml.Deserialize(fsxmlr) as List<Shape>;
foreach (Shape item in loadedShapesXml)
{
WriteLine("{0} is {1} and has an area of {2:N2}",
item.GetType().Name, item.Colour, item.Area);
}
fsxmlr.Close();