views:

7424

answers:

8

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.List`1[[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.

+1  A: 

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;
}
Andreas Grech
OK perhaps, but this still doesn't do what I want - serialize a list of objects, possibly of different types. I want to be able to add a bunch of objects I want to save to a list, then serialize the entire list in one go (and to one file).
Simon D
I don't like this option, because creating XmlSerializers is expensive.
Ian
Ian, Would you please let me know how do you find XmlSerializers expensive?
paradisonoir
This will cause a memory leak.
Rex M
paradisonoir, construction of them creates temporary serialization assemblies for the given type (and all referenced types that need to be deserialized). If you have a large object graoh that needs serializing or just a large number of objects and you're repeatedly calling XmlSerializer then it can be slow. We found that it was taking 10-13 seconds to serialize and deserialize some settings, which was made almost instant by pre-building these serialization assemblies.
Ian
Why will this cause a memory leak?
Simon D
@Fred the XmlSerializer(Type) constructor creates a temp assembly to handle the serialization for that type and adds it to the AppDomain, but if you call it a second time for the same type, it does not re-use the same temp assembly, it creates another one. Since assemblies cannot be removed from the AppDomain once they are loaded, every time you invoke the constructor your memory footprint will increase slightly.
Rex M
In an application like the one I work on daily, if each new temp assembly represented 1kb, we would be leaking 200 gigabytes per day. By carefully managing the XmlSerializers in a type cache, we keep the same set of serializers in memory, at a total cost of about 1 megabyte of cache.
Rex M
Thanks Rex M interesting stuff.
Simon D
@Dreas: you declared the filestream after you used it, and didn't enclose it in a `using` block.
John Saunders
+1  A: 

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();
}
Ian
This looks interesting, but I'm not sure how to use it exactly. I'll try to play with it a bit and see if I can get it to work.
Simon D
Ok, firstly it all the objects in the collection need to be strongly typed to the same base type. The objects need to implement IXmlSerializable (not too hard).For a collection e.g. List<T> you'll then call something like:XmlWriter.SaveXmlSerializableCollection("item", this.collectionOfTs)
Ian
btw, do you need to deserialize them too? If not I can probably provide a fuller example for you...
Ian
I probably will deserialize, but that's already a fair bit to be getting on with. I'll try and put what you've given me already into practice. Thanks very much.
Simon D
A: 

Here's a related question on how to (de)serialize a collection of objects that belong to classes deriving from the same base type.

azheglov
+2  A: 

See Introducing XML Serialization:

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.

John Saunders
+1 for the remark about ISerializable and [Serializable]
Thomas Levesque
http://msdn.microsoft.com/en-us/library/182eeyhh%28VS.85%29.aspx - link got mangled, thanks for this I'm understanding more...lots to read.
Simon D
Also thanks for the advice on how to ask questions, it's appreciated.
Simon D
+1. Yes, they don't matter for XML, but if he ever wants to use other types of serialization, they will. Also, since he was casting to ISerializable, it seemed like a perfectly basic question for him to check that the different types implemented that interface. I hope it wasn't a dig at my comment, and I think this was helpful in clarifying some of these details.
Erich Mirabal
@Erich: don't be so sensitive. It's a fact, not a dig. I'm constantly seeing XML Serialization examples with `[Serializable]`, which is just plain wrong.
John Saunders
@Fred: Glad to help, but it's self-defense. I hope to see many more questions from you - and I hope they include details! BTW, when you post about an exception causing you problems, catch it, then post the result of `ex.ToString()`. That tells us everything.
John Saunders
Great tip, thanks!
Simon D
I was just saying that there is a difference in calling an object serializable and serializing it in XML. I didn't take it too personally or sensitively; I actually up-voted your answer as it brought up some good points about serializing in XML that are never mentioned. Still, they may not be *required* for XML, but it usually is good practice so that you can easily support other serializations.
Erich Mirabal
@Erich: I have to disagree. The various serialization technologies are too different. It makes no sense to support those you're not going to use. Consider: did you plan to _test_ the ones you're not using? In my opinion, if you didn't test it, then it doesn't work. Why produce code that you know you're not going to test?
John Saunders
Well, for example `xsd.exe` marks its generated classes with `[Serializable]` to give you more flexibility (since you are already serializing to XML, you may also want to serialize to binary, for example). I think "not test -> not working" is a good philosophy and rule of thumb, but I've had plenty of cases (for reasons outside of my control) where I've had to produce code that I could not test other than running it as an end-product. Most of the time, if I've serialized it as XML, I have also had to support it as binary (or vice-versa). That's the nature of serialization in my experience.
Erich Mirabal
I would not count xsd.exe as an especially good example. In fact, it's the .NET Framework code it's calling that sets the attributes, and its entire _purpose_ is to be general. That has no bearing on how anyone else should design their code.
John Saunders
A: 

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);
    }
Robert Venables
Nice example. This is more what I was thinking - a number of different objects and I want to put them into one file. I prefer XML to binary though, but will try this.
Simon D
A: 

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))
{
    xmlSerializer.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
{
}
Thomas Levesque
Hmm, that looks interesting. I'm assuming though it still won't deserialize?
Ian
It *will* deserialize. Actually, during serialization, an extra attribute `xsi:type` is written on the element that represents the object. This allows XmlSerializer to know the actual type of the object
Thomas Levesque
+1  A: 

Below is a Util class in my project:

using System;

using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.IO; using System.Xml.Serialization;

namespace Utils { public static class SerializeUtil { public static void SerializeToFormatter(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);
        }
    }



}

}

ligaoren
A: 

You have made the zero parameter constructor private. I'm pretty sure the serializer needs access to that constructor.

MrPhil