views:

872

answers:

6

OK, so here's the story so far.

I could already deserialize individual objects using XmlSerializer, but deserializing lists was proving to be a real headache. I started out by trying to serialize List<Foo> and the serializer serialized multiple <Foo> XML structures inside a root <ArrayOfFoo> element. That proved to be problematic to deserialize, so it looks like I needed to have defined the 'ArrayOfFoo' element myself. So, I've got a class working that is a 'wrapper' for the list, as shown in this program:

using System;
using System.IO;
using System.Collections.Generic;
using System.Xml.Serialization;

namespace XmlTester2
{
    public class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("XML tester...");

            string xml =
                "<ItemList xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"&gt;" +
                "<Person i:type=\"PersonI2\">" + "<Field1>field1Val</Field1>" +
                "<Field2>field2Val</Field2>" + "<Field3>field3Val</Field3>" +
                "<Field4>field4Val</Field4>" + "</Person>" +
                "<Account i:type=\"AccountI2\">" + "<Field1>field1Val</Field1>" +
                "<Field2>field2Val</Field2>" + "<Field3>field3Val</Field3>" +
                "<Field4>field4Val</Field4>" + "</Account>" +
                "<Person i:type=\"PersonI2\">" + "<Field1>field1Val</Field1>" +
                "<Field2>field2Val</Field2>" + "<Field3>field3Val</Field3>" +
                "<Field4>field4Val</Field4>" + "</Person>" + "</ItemList>";

            XmlSerializer ser = new XmlSerializer(typeof(ItemList));

            using (var reader = new StringReader(xml))
            {
                ItemList result = (ItemList)ser.Deserialize(reader);
            }

            Console.WriteLine("Break here and check 'result' in Quickwatch...");
            Console.ReadKey();
        }
    }

    [XmlRootAttribute(IsNullable = false)]
    public class ItemList
    {
        [XmlElementAttribute("Person")]
        public List<Person> Persons { get; set; }

        [XmlElementAttribute("Account")]
        public List<Account> Accounts { get; set; }
    }

    [XmlTypeAttribute(AnonymousType = false, TypeName = "Person", Namespace = "")]
    [XmlInclude(typeof(PersonI2))]
    public class Person
    {
        public string Field1 { get; set; }
        public string Field2 { get; set; }
        public string Field3 { get; set; }
    }

    [XmlTypeAttribute(AnonymousType = false, TypeName = "PersonI2", Namespace = "")]
    public class PersonI2 : Person
    {
        public string Field4 { get; set; }
    }

    [XmlTypeAttribute(AnonymousType = false, TypeName = "Account", Namespace = "")]
    [XmlInclude(typeof(AccountI2))]
    public class Account
    {
        public string Field1 { get; set; }
        public string Field2 { get; set; }
        public string Field3 { get; set; }
    }

    [XmlTypeAttribute(AnonymousType = false, TypeName = "AccountI2", Namespace = "")]
    public class AccountI2 : Account
    {
        public string Field4 { get; set; }
    }
}

However, this 'wrapper', ItemList, still has to have manually defined in it all the elements that might be contained (in the example, Person and Account). What would be really ideal would be to have a generic list wrapper class. I know this is a bit hopeful, but would there be a way to do this? I'm thinking of something along these lines (this does not work, but is just to give you the general idea):

using System;
using System.IO;
using System.Collections.Generic;
using System.Xml.Serialization;

namespace XmlTester3
{
    public class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("XML tester...");

            string xml =
                "<ItemList xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"&gt;" +
                "<Person i:type=\"PersonI2\">" + 
                "<Field1>field1Val</Field1>" +
                "<Field2>field2Val</Field2>" + 
                "<Field3>field3Val</Field3>" +
                "<Field4>field4Val</Field4>" + 
                "</Person>" +
                "<Person i:type=\"PersonI2\">" + 
                "<Field1>field1Val</Field1>" +
                "<Field2>field2Val</Field2>" + 
                "<Field3>field3Val</Field3>" +
                "<Field4>field4Val</Field4>" + 
                "</Person>" + 
                "</ItemList>";

            XmlSerializer ser = new XmlSerializer(typeof(ItemList<Person>));

            using (var reader = new StringReader(xml))
            {
                ItemList<Person> result = (ItemList<Person>)ser.Deserialize(reader);
            }

            Console.WriteLine("Break here and check 'result' in Quickwatch...");
            Console.ReadKey();
        }
    }

    [XmlRootAttribute(IsNullable = false)]
    [XmlInclude(typeof(Person))]
    [XmlInclude(typeof(PersonI2))]
    [XmlInclude(typeof(Account))]
    [XmlInclude(typeof(AccountI2))]
    public class ItemList<T>
    {
        [XmlElementAttribute]
        public List<T> Items { get; set; }
    }

    [XmlTypeAttribute(AnonymousType = false, TypeName = "Person", Namespace = "")]
    [XmlInclude(typeof(PersonI2))]
    public class Person
    {
        public string Field1 { get; set; }
        public string Field2 { get; set; }
        public string Field3 { get; set; }
    }

    [XmlTypeAttribute(AnonymousType = false, TypeName = "PersonI2", Namespace = "")]
    public class PersonI2 : Person
    {
        public string Field4 { get; set; }
    }

    [XmlTypeAttribute(AnonymousType = false, TypeName = "Account", Namespace = "")]
    [XmlInclude(typeof(AccountI2))]
    public class Account
    {
        public string Field1 { get; set; }
        public string Field2 { get; set; }
        public string Field3 { get; set; }
    }

    [XmlTypeAttribute(AnonymousType = false, TypeName = "AccountI2", Namespace = "")]
    public class AccountI2 : Account
    {
        public string Field4 { get; set; }
    }
}

So, the XML structures passed inside the ItemList would only be able to be of one type, say Person in this example, and I could define an ItemList<Person> that would allow me to deserialize a list containing multiple Person objects? Any ideas? If necessary, I wouldn't mind having to tag the ItemList class with an [XmlInclude...] for every type that ItemList might contain a collection of.

I'm guessing this is possible, I just haven't figured out quite how? :-) Or is the default XmlSerializer too fussy?

+2  A: 

I'm not sure I understand your question but do you know there's a XmlArrayItemAttribute.

[XmlArray("foos"), XmlArrayItem(typeof(Foo), ElementName = "foo")]
bruno conde
The trouble with that is that you're having to hardcode in typeof(Foo) and the associated element name, so it can't be a generic.
Jez
+1  A: 

Under .NET 3.5 SP1 (Specificly SP1) you can use the Serializers from WCF to deserialize objects without specificly marking the class up with DataContract or Serializable attributes.

Almost any class should be able to be deserialized this way - as long as the Property names match the element names.

If you're getting deserializer errors - then it's possibly because of some misnamed property or an incorrect type. To check the input that the Serializer is looking for, you can populate an object once, and then serialize it down to XML to compare.

I wrote myself a helper class for using this a while back.

The way to use the helper is:

string serialized = "some xml";
MyType foo = Helpers.Deserialize<MyType>(serialized, SerializerType.Xml);

The actual helper class:

using System.IO;
using System.Runtime.Serialization; // System.Runtime.Serialization.dll (.NET 3.0)
using System.Runtime.Serialization.Json; // System.ServiceModel.Web.dll (.NET 3.5)
using System.Text;
namespace Serialization
{
    public static class Helpers
    {
        /// <summary>
        /// Declare the Serializer Type you want to use.
        /// </summary>
        public enum SerializerType
        {
            Xml, // Use DataContractSerializer
            Json // Use DataContractJsonSerializer
        }

        public static T Deserialize<T>(string SerializedString, SerializerType UseSerializer)
        {
            // Get a Stream representation of the string.
            using (Stream s = new MemoryStream(UTF8Encoding.UTF8.GetBytes(SerializedString)))
            {
                T item;
                switch (UseSerializer)
                {
                    case SerializerType.Json:
                        // Declare Serializer with the Type we're dealing with.
                        var serJson = new DataContractJsonSerializer(typeof(T));
                        // Read(Deserialize) with Serializer and cast
                        item = (T)serJson.ReadObject(s);
                        break;
                    case SerializerType.Xml:
                    default:
                        var serXml = new DataContractSerializer(typeof(T));
                        item = (T)serXml.ReadObject(s);
                        break;
                }
                return item;
            }
        }

        public static string Serialize<T>(T ObjectToSerialize, SerializerType UseSerializer)
        {
            using (MemoryStream serialiserStream = new MemoryStream())
            {
                string serialisedString = null;
                switch (UseSerializer)
                {
                    case SerializerType.Json:
                        // init the Serializer with the Type to Serialize
                        DataContractJsonSerializer serJson = new DataContractJsonSerializer(typeof(T));
                        // The serializer fills the Stream with the Object's Serialized Representation.
                        serJson.WriteObject(serialiserStream, ObjectToSerialize);
                        break;
                    case SerializerType.Xml:
                    default:
                        DataContractSerializer serXml = new DataContractSerializer(typeof(T));
                        serXml.WriteObject(serialiserStream, ObjectToSerialize);
                        break;
                }
                // Rewind the stream to the start so we can now read it.
                serialiserStream.Position = 0;
                using (StreamReader sr = new StreamReader(serialiserStream))
                {
                    // Use the StreamReader to get the serialized text out
                    serialisedString = sr.ReadToEnd();
                    sr.Close();
                }
                return serialisedString;
            }
        }
    }
}
Will Hughes
That's just the problem though; if I want a generic ItemList wrapper class allowing me to serialize/deserialize lists of generic items, I can't know the name of the properties that will need to be inside the ItemList when deserializing - they will differ according to the type that's been serialized. I need a generic way to vary what the deserializer will expect... eg. if I create an ItemList<Foo>, it expects <ItemList><Foo>[...]</Foo></ItemList>
Jez
Ah, I misunderstood. I thought you already knew the properties ahead of time and had a class to deserialize to. If you don't know the properties at compile time, then prior to .NET 4.0 you would need to access the properties via dictionary, or just xpath.
Will Hughes
+3  A: 

You can do this easily enough, just implement the System.Xml.Serialization.IXmlSerializable interface. If I were doing this, I might even reflect the possible derived types of T in the assembly that defines T and completely omit the [XmlInclude] declarations. The real down side with this approach is the creation of the XmlSerializers. You might consider caching them. Anyway just use this in your second example and it should work.

BTW, that is an interesting thing your doing with the "i:type=\"PersonI2\""; props for figuring that one out ;)

[XmlRootAttribute("ItemList", IsNullable = false)]
[XmlInclude(typeof(Person))]
[XmlInclude(typeof(PersonI2))]
[XmlInclude(typeof(Account))]
[XmlInclude(typeof(AccountI2))]
public class ItemList<T> : System.Xml.Serialization.IXmlSerializable
{
 class Map : Dictionary<String, XmlSerializer> 
 { public Map() : base(StringComparer.Ordinal) { } }

 public List<T> Items { get; set; }

 public System.Xml.Schema.XmlSchema GetSchema()
 {
  return null;
 }

 private string TypeName(Type t)
 {
  String typeName = t.Name;
  foreach (XmlTypeAttribute a in t.GetCustomAttributes(typeof(XmlTypeAttribute), true))
   if (!String.IsNullOrEmpty(a.TypeName))
    typeName = a.TypeName;
  return typeName;
 }

 private Map LoadSchema()
 {
  Map map = new Map();
  foreach (XmlIncludeAttribute inc in typeof(ItemList<T>).GetCustomAttributes(typeof(XmlIncludeAttribute), true))
  {
   Type t = inc.Type;
   if (typeof(T).IsAssignableFrom(t))
    map.Add(TypeName(t), new XmlSerializer(t));
  }
  return map;
 }

 public void ReadXml(System.Xml.XmlReader reader)
 {
  Map map = LoadSchema();
  int depth = reader.Depth;

  List<T> items = new List<T>();
  if (!reader.IsEmptyElement && reader.Read())
  {
   while (reader.Depth > depth)
   {
    items.Add((T)map[reader.LocalName].Deserialize(reader));
   }
  }
  this.Items = items;
 }

 public void WriteXml(System.Xml.XmlWriter writer)
 {
  Map map = LoadSchema();
  foreach (T item in this.Items)
  {
   map[TypeName(item.GetType())].Serialize(writer, item);
  }
 }
}
csharptest.net
A: 
Danny Varod
A: 

UPDATE: Please see the answer beginning !THIS IS THE BEST SOLUTION I'VE FOUND! - it's a better solution than this one.

...

Heavily inspired by csharptest.net's comment, I've created a class that pretty much does the job I wanted. :-) You access the deserialized items by checking ItemList.Items, and serialize stuff by inserting the items into ItemList.Items and then serializing it using an appropriate XmlSerializer. The only slight annoyance is that you must ensure that the ItemList class is tagged with an XmlIncludeAttribute for every class type that may need to be (de)serialized, or the XmlSerializer won't be able to deal with it.

Here's the example program, containing the generic ItemList class:

using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace XmlTester
{
 public class Program {
  static void Main(string[] args) {
   Console.WriteLine("XML tester...");

// Valid XML for an ItemList of Person's
XmlSerializer ser = new XmlSerializer(typeof(ItemList<Person>));
string xmlIn =
@"<ItemList xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""&gt;
 <PersonBilingual>
  <FullName>John Smith</FullName>
  <Age>21</Age>
  <Language>French</Language>
  <SecondLanguage>German</SecondLanguage>
 </PersonBilingual>
 <Person>
  <FullName>Joe Bloggs</FullName>
  <Age>26</Age>
  <Language>English</Language>
 </Person>
 <Person i:type=""PersonBilingual"">
  <FullName>Jane Doe</FullName>
  <Age>78</Age>
  <Language>Italian</Language>
  <SecondLanguage>English</SecondLanguage>
 </Person>
</ItemList>";

//// Valid XML for an ItemList of Account's
//XmlSerializer ser = new XmlSerializer(typeof(ItemList<Account>));
//string xmlIn =
//@"<ItemList xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""&gt;
// <AccountBank>
//  <AcctName>Deposit account</AcctName>
//  <WithCompany>Bank of Switzerland</WithCompany>
//  <BalanceInEuros>300</BalanceInEuros>
// </AccountBank>
// <Account>
//  <AcctName>Book buying account</AcctName>
//  <WithCompany>Amazon</WithCompany>
// </Account>
// <Account i:type=""AccountBank"">
//  <AcctName>Savings account</AcctName>
//  <WithCompany>Bank of America</WithCompany>
//  <BalanceInEuros>2500</BalanceInEuros>
// </Account>
//</ItemList>";

//// Invalid XML, as we have mixed incompatible types
//XmlSerializer ser = new XmlSerializer(typeof(ItemList<Person>));
//string xmlIn =
//@"<ItemList xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""&gt;
// <PersonBilingual>
//  <FullName>John Smith</FullName>
//  <Age>21</Age>
//  <Language>French</Language>
//  <SecondLanguage>German</SecondLanguage>
// </PersonBilingual>
// <Account>
//  <AcctName>Book buying account</AcctName>
//  <WithCompany>Amazon</WithCompany>
// </Account>
// <Person i:type=""PersonBilingual"">
//  <FullName>Jane Doe</FullName>
//  <Age>78</Age>
//  <Language>Italian</Language>
//  <SecondLanguage>English</SecondLanguage>
// </Person>
//</ItemList>";

   // Deserialize...
   ItemList<Person> result;
   using (var reader = new StringReader(xmlIn)) {
    result = (ItemList<Person>)ser.Deserialize(reader);
   }

   Console.WriteLine("Break here and check 'result' in Quickwatch...");
   Console.ReadKey();

   // Serialize...
   StringBuilder xmlOut = new StringBuilder();
   ser.Serialize(new StringWriter(xmlOut), result);

   Console.WriteLine("Break here and check 'xmlOut' in Quickwatch...");
   Console.ReadKey();
  }
 }

 [XmlRoot(ElementName = "ItemList", IsNullable = false)]
 [XmlInclude(typeof(Person))]
 [XmlInclude(typeof(PersonBilingual))]
 [XmlInclude(typeof(Account))]
 [XmlInclude(typeof(AccountBank))]
 public class ItemList<T> : IXmlSerializable {
  #region Private vars

  /// <summary>
  /// The class that will store our serializers for the various classes that may be (de)serialized, given
  /// the type of this ItemList (ie. the type itself, as well as any type that extends the type)
  /// </summary>
  private class Map : Dictionary<string, XmlSerializer> { public Map() : base(StringComparer.Ordinal) { } }

  #endregion

  #region Private methods

  /// <summary>
  /// Creates a 'schema' for this ItemList, using its type, and the XmlIncludeAttribute types that are
  /// associated with it.  For each XmlIncludeAttribute, if it can be assigned to this ItemList's type (so
  /// it's either the same type as this ItemList's type or a type that extends this ItemList's type), adds
  /// the XmlSerializer for that XmlIncludeAttribute's type to our 'schema' collection, allowing a node
  /// corresponding to that type to be (de)serialized by this ItemList.
  /// </summary>
  /// <returns>The 'schema' containing the XmlSerializer's available for this ItemList to use during (de)serialization.</returns>
  private Map loadSchema() {
   Map map = new Map();
   foreach (XmlIncludeAttribute inc in typeof(ItemList<T>).GetCustomAttributes(typeof(XmlIncludeAttribute), true)) {
    Type t = inc.Type;
    if (typeof(T).IsAssignableFrom(t)) { map.Add(xmlTypeName(t), new XmlSerializer(t)); }
   }
   return map;
  }

  /// <summary>
  /// As the XML type name can be different to our internal class name for that XML type, we need to be able
  /// to expect an XML element name that is different to our internal class name for that XML type.  Hence,
  /// our 'schema' map will contain XmlSerializer's whose keys are based on the XML type name, NOT our
  /// internal class name for that XML type.  This method returns the XML type name given our internal
  /// class we're using to (de)serialize that XML type.  If no XML TypeName is specified in our internal
  /// class's XmlTypeAttribute, we assume an XML type name identical to the internal class name.
  /// </summary>
  /// <param name="t">Our internal class used to (de)serialize an XML type.</param>
  /// <returns>The XML type name corresponding to the given internal class.</returns>
  private string xmlTypeName(Type t) {
   string typeName = t.Name;
   foreach (XmlTypeAttribute ta in t.GetCustomAttributes(typeof(XmlTypeAttribute), true)) {
    if (!string.IsNullOrEmpty(ta.TypeName)) { typeName = ta.TypeName; }
   }
   return typeName;
  }

  #endregion

  #region IXmlSerializable Members

  /// <summary>
  /// Reserved and should not be used.
  /// </summary>
  /// <returns>Must return null.</returns>
  public XmlSchema GetSchema() {
   return null;
  }

  /// <summary>
  /// Generates a list of type T objects from their XML representation; stores them in this.Items.
  /// </summary>
  /// <param name="reader">The System.Xml.XmlReader stream from which the objects are deserialized.</param>
  public void ReadXml(XmlReader reader) {
   Map map = loadSchema();
   int depth = reader.Depth;

   List<T> items = new List<T>();
   if (!reader.IsEmptyElement && reader.Read()) {
    // While the reader is at a greater depth that the initial depth, ie. at one of the elements
    // inside the list wrapper, the initial depth being that of the list wrapper <ItemList>...
    while (reader.Depth > depth) {
     try { items.Add((T)map[reader.LocalName].Deserialize(reader)); }
     catch (InvalidOperationException iopEx) {
      if (
       iopEx.InnerException != null &&
       iopEx.InnerException.Message.StartsWith("The specified type was not recognized")
      ) { throw new InvalidOperationException("Couldn't deserialize node '" + reader.LocalName + "' because although its element node is a valid type, its attribute-specified type was not recognized.  Perhaps it needs adding to the ItemList using XmlIncludeAttribute?", iopEx); }
     }
     catch (KeyNotFoundException knfEx) {
      throw new InvalidOperationException("Couldn't deserialize node '" + reader.LocalName + "' because its element node was not recognized as a valid type.  Perhaps it needs adding to the ItemList using XmlIncludeAttribute?", knfEx);
     }
     catch (Exception ex) {
      throw ex;
     }
    }
   }
   this.Items = items;
  }

  /// <summary>
  /// Converts a list of type T objects into their XML representation; writes them to the specified writer.
  /// </summary>
  /// <param name="writer">The System.Xml.XmlWriter stream to which the objects are serialized.</param>
  public void WriteXml(XmlWriter writer) {
   Map map = loadSchema();
   foreach (T item in this.Items) {
    map[xmlTypeName(item.GetType())].Serialize(writer, item);
   }
  }

  #endregion

  #region Public properties

  public List<T> Items { get; set; }

  #endregion
 }

 /// <summary>
 /// A regular person.
 /// </summary>
 [XmlType(AnonymousType = false, TypeName = "Person", Namespace = "")]
 [XmlInclude(typeof(PersonBilingual))]
 public class Person {
  public string FullName { get; set; }
  public int Age { get; set; }
  public string Language { get; set; }
 }

 /// <summary>
 /// A person who can speak a second language.
 /// </summary>
 [XmlType(AnonymousType = false, TypeName = "PersonBilingual", Namespace = "")]
 public class PersonBilingual : Person {
  public string SecondLanguage { get; set; }
 }

 /// <summary>
 /// Some kind of account.
 /// </summary>
 [XmlType(AnonymousType = false, TypeName = "Account", Namespace = "")]
 [XmlInclude(typeof(AccountBank))]
 public class Account {
  public string AcctName { get; set; }
  public string WithCompany { get; set; }
 }

 /// <summary>
 /// A bank account.
 /// </summary>
 [XmlType(AnonymousType = false, TypeName = "AccountBank", Namespace = "")]
 public class AccountBank : Account {
  public int BalanceInEuros { get; set; }
 }
}

Thanks everyone for your help!

Jez
I found an even more elegant way of designing ItemList, just after posting this. Please see my other answer to this question to see the even better way.
Jez
A: 

!THIS IS THE BEST SOLUTION I'VE FOUND!

OK, sorry for the answer-spam here, people, but I've found an even more elegant way of doing this that avoids the need for ItemList to have its items accessed using an 'Items' property; make the ItemList a List itself! This way, you just directly access ItemList as a list. Here's the amended example program:

using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace XmlTester
{
    public class Program {
     static void Main(string[] args) {
      Console.WriteLine("XML tester...");

// Valid XML for an ItemList of Person's
XmlSerializer ser = new XmlSerializer(typeof(ItemList<Person>));
string xmlIn =
@"<ItemList xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""&gt;
    <PersonBilingual>
     <FullName>John Smith</FullName>
     <Age>21</Age>
     <Language>French</Language>
     <SecondLanguage>German</SecondLanguage>
    </PersonBilingual>
    <Person>
     <FullName>Joe Bloggs</FullName>
     <Age>26</Age>
     <Language>English</Language>
    </Person>
    <Person i:type=""PersonBilingual"">
     <FullName>Jane Doe</FullName>
     <Age>78</Age>
     <Language>Italian</Language>
     <SecondLanguage>English</SecondLanguage>
    </Person>
</ItemList>";

//// Valid XML for an ItemList of Account's
//XmlSerializer ser = new XmlSerializer(typeof(ItemList<Account>));
//string xmlIn =
//@"<ItemList xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""&gt;
//  <AccountBank>
//   <AcctName>Deposit account</AcctName>
//   <WithCompany>Bank of Switzerland</WithCompany>
//   <BalanceInEuros>300</BalanceInEuros>
//  </AccountBank>
//  <Account>
//   <AcctName>Book buying account</AcctName>
//   <WithCompany>Amazon</WithCompany>
//  </Account>
//  <Account i:type=""AccountBank"">
//   <AcctName>Savings account</AcctName>
//   <WithCompany>Bank of America</WithCompany>
//   <BalanceInEuros>2500</BalanceInEuros>
//  </Account>
//</ItemList>";

//// Invalid XML, as we have mixed incompatible types
//XmlSerializer ser = new XmlSerializer(typeof(ItemList<Person>));
//string xmlIn =
//@"<ItemList xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""&gt;
//  <PersonBilingual>
//   <FullName>John Smith</FullName>
//   <Age>21</Age>
//   <Language>French</Language>
//   <SecondLanguage>German</SecondLanguage>
//  </PersonBilingual>
//  <Account>
//   <AcctName>Book buying account</AcctName>
//   <WithCompany>Amazon</WithCompany>
//  </Account>
//  <Person i:type=""PersonBilingual"">
//   <FullName>Jane Doe</FullName>
//   <Age>78</Age>
//   <Language>Italian</Language>
//   <SecondLanguage>English</SecondLanguage>
//  </Person>
//</ItemList>";

      // Deserialize...
      ItemList<Person> result;
      using (var reader = new StringReader(xmlIn)) {
       result = (ItemList<Person>)ser.Deserialize(reader);
      }

      Console.WriteLine("Break here and check 'result' in Quickwatch...");
      Console.ReadKey();

      // Serialize...
      StringBuilder xmlOut = new StringBuilder();
      ser.Serialize(new StringWriter(xmlOut), result);

      Console.WriteLine("Break here and check 'xmlOut' in Quickwatch...");
      Console.ReadKey();
     }
    }

    [XmlRoot(ElementName = "ItemList", IsNullable = false)]
    [XmlInclude(typeof(Person))]
    [XmlInclude(typeof(PersonBilingual))]
    [XmlInclude(typeof(Account))]
    [XmlInclude(typeof(AccountBank))]
    public class ItemList<T> : List<T>, IXmlSerializable {
     #region Private vars

     /// <summary>
     /// The class that will store our serializers for the various classes that may be (de)serialized, given
     /// the type of this ItemList (ie. the type itself, as well as any type that extends the type)
     /// </summary>
     private class Map : Dictionary<string, XmlSerializer> { public Map() : base(StringComparer.Ordinal) { } }

     #endregion

     #region Private methods

     /// <summary>
     /// Creates a 'schema' for this ItemList, using its type, and the XmlIncludeAttribute types that are
     /// associated with it.  For each XmlIncludeAttribute, if it can be assigned to this ItemList's type (so
     /// it's either the same type as this ItemList's type or a type that extends this ItemList's type), adds
     /// the XmlSerializer for that XmlIncludeAttribute's type to our 'schema' collection, allowing a node
     /// corresponding to that type to be (de)serialized by this ItemList.
     /// </summary>
     /// <returns>The 'schema' containing the XmlSerializer's available for this ItemList to use during (de)serialization.</returns>
     private Map loadSchema() {
      Map map = new Map();
      foreach (XmlIncludeAttribute inc in typeof(ItemList<T>).GetCustomAttributes(typeof(XmlIncludeAttribute), true)) {
       Type t = inc.Type;
       if (typeof(T).IsAssignableFrom(t)) { map.Add(xmlTypeName(t), new XmlSerializer(t)); }
      }
      return map;
     }

     /// <summary>
     /// As the XML type name can be different to our internal class name for that XML type, we need to be able
     /// to expect an XML element name that is different to our internal class name for that XML type.  Hence,
     /// our 'schema' map will contain XmlSerializer's whose keys are based on the XML type name, NOT our
     /// internal class name for that XML type.  This method returns the XML type name given our internal
     /// class we're using to (de)serialize that XML type.  If no XML TypeName is specified in our internal
     /// class's XmlTypeAttribute, we assume an XML type name identical to the internal class name.
     /// </summary>
     /// <param name="t">Our internal class used to (de)serialize an XML type.</param>
     /// <returns>The XML type name corresponding to the given internal class.</returns>
     private string xmlTypeName(Type t) {
      string typeName = t.Name;
      foreach (XmlTypeAttribute ta in t.GetCustomAttributes(typeof(XmlTypeAttribute), true)) {
       if (!string.IsNullOrEmpty(ta.TypeName)) { typeName = ta.TypeName; }
      }
      return typeName;
     }

     #endregion

     #region IXmlSerializable Members

     /// <summary>
     /// Reserved and should not be used.
     /// </summary>
     /// <returns>Must return null.</returns>
     public XmlSchema GetSchema() {
      return null;
     }

     /// <summary>
     /// Generates a list of type T objects from their XML representation; stores them in this ItemList.
     /// </summary>
     /// <param name="reader">The System.Xml.XmlReader stream from which the objects are deserialized.</param>
     public void ReadXml(XmlReader reader) {
      Map map = loadSchema();
      int depth = reader.Depth;

      List<T> items = new List<T>();
      if (!reader.IsEmptyElement && reader.Read()) {
       // While the reader is at a greater depth that the initial depth, ie. at one of the elements
       // inside the list wrapper, the initial depth being that of the list wrapper <ItemList>...
       while (reader.Depth > depth) {
        try { items.Add((T)map[reader.LocalName].Deserialize(reader)); }
        catch (InvalidOperationException iopEx) {
         if (
          iopEx.InnerException != null &&
          iopEx.InnerException.Message.StartsWith("The specified type was not recognized")
         ) { throw new InvalidOperationException("Couldn't deserialize node '" + reader.LocalName + "' because although its element node is a valid type, its attribute-specified type was not recognized.  Perhaps it needs adding to the ItemList using XmlIncludeAttribute?", iopEx); }
        }
        catch (KeyNotFoundException knfEx) {
         throw new InvalidOperationException("Couldn't deserialize node '" + reader.LocalName + "' because its element node was not recognized as a valid type.  Perhaps it needs adding to the ItemList using XmlIncludeAttribute?", knfEx);
        }
        catch (Exception ex) {
         throw ex;
        }
       }
      }
      this.AddRange(items);
     }

     /// <summary>
     /// Converts a list of type T objects into their XML representation; writes them to the specified writer.
     /// </summary>
     /// <param name="writer">The System.Xml.XmlWriter stream to which the objects are serialized.</param>
     public void WriteXml(XmlWriter writer) {
      Map map = loadSchema();
      foreach (T item in this) {
       map[xmlTypeName(item.GetType())].Serialize(writer, item);
      }
     }

     #endregion
    }

    /// <summary>
    /// A regular person.
    /// </summary>
    [XmlType(AnonymousType = false, TypeName = "Person", Namespace = "")]
    [XmlInclude(typeof(PersonBilingual))]
    public class Person {
     public string FullName { get; set; }
     public int Age { get; set; }
     public string Language { get; set; }
    }

    /// <summary>
    /// A person who can speak a second language.
    /// </summary>
    [XmlType(AnonymousType = false, TypeName = "PersonBilingual", Namespace = "")]
    public class PersonBilingual : Person {
     public string SecondLanguage { get; set; }
    }

    /// <summary>
    /// Some kind of account.
    /// </summary>
    [XmlType(AnonymousType = false, TypeName = "Account", Namespace = "")]
    [XmlInclude(typeof(AccountBank))]
    public class Account {
     public string AcctName { get; set; }
     public string WithCompany { get; set; }
    }

    /// <summary>
    /// A bank account.
    /// </summary>
    [XmlType(AnonymousType = false, TypeName = "AccountBank", Namespace = "")]
    public class AccountBank : Account {
     public int BalanceInEuros { get; set; }
    }
}
Jez