views:

3001

answers:

7

A base project contains an abstract base class Foo. In separate client projects, there are classes implementing that base class.

I'd like to serialize and restore an instance of a concrete class by calling some method on the base class:

// In the base project:
public abstract class Foo
{
    abstract void Save (string path);
    abstract Foo Load (string path);
}

It can be assumed that at the time of deserialization, all needed classes are present. If possible in any way, the serialization should be done in XML. Making the base class implement IXmlSerializable is possible.

I'm a bit stuck here. If my understanding of things is correct, then this is only possible by adding an [XmlInclude(typeof(UnknownClass))] to the base class for every implementing class - but the implementing classes are unknown!

Is there a way to do this? I've got no experience with reflection, but i also welcome answers using it.

Edit: The problem is Deserializing. Just serializing would be kind of easy. :-)

+1  A: 

Somewhere deep inside the XML namespaces lies a wonderful class called XmlReflectionImporter.

This may be of help to you if you need to create a schema at runtime.

leppie
+1  A: 

You can also do this by creating an XmlSerializer passign in all possible types to the constructor. Be warned that when you use this constructor the xmlSerializer will be compiled each and every time and will result in a leak if you constantly recreate it. You will want to create a single serializer and reuse it in your application.

You can then bootstrap the serializer and using reflection look for any descendants of foo.

JoshBerke
+2  A: 

Well the serialization shouldn't be a problem, the XmlSerializer constructor takes a Type argument, even calling GetType on an instance of a derived class through a method on the abstract base will return the derived types actual Type. So in essence as long as you know the proper type upon deserialization then the serialization of the proper type is trivial. So you can implement a method on the base called serialize or what have you that passes this.GetType() to the constructor of the XmlSerializer.. or just passes the current reference out and lets the serialize method take care of it and you should be fine.

Edit: Update for OP Edit..

If you don't know the type at deserialization then you really have nothing but a string or byte array, without some sort of identifier somewhere you are kind of up a creek. There are some things you can do like trying to deserialize as every known derived type of the xx base class, I would not recommend this.

Your other option is to walk the XML manually and reconstruct an object by embedding the type as a property or what have you, maybe that is what you originally meant in the article, but as it stands I don't think there is a way for the built in serialization to take care of this for you without you specifying the type.

Quintin Robinson
+2  A: 

You don't have to put the serialization functions into any base class, instead, you can add it to your Utility Class.

e.g. ( the code is for example only, rootName is optional )

public static class Utility
{
       public static void ToXml<T>(T src, string rootName, string fileName) where T : class, new()
        {
            XmlSerializer serializer = new XmlSerializer(typeof(T), new XmlRootAttribute(rootName));
            XmlTextWriter writer = new XmlTextWriter(fileName, Encoding.UTF8);
            serializer.Serialize(writer, src);
            writer.Flush();
            writer.Close();
        }
}

Simply make call to

Utility.ToXml( fooObj, "Foo", @"c:\foo.xml");

Not only Foo's family types can use it, but all other serializable objects.

EDIT

OK full service... (rootName is optional)

public static T FromXml<T>(T src, string rootName, string fileName) where T : class, new()
{
    XmlSerializer serializer = new XmlSerializer(typeof(T), new XmlRootAttribute(rootName));
    TextReader reader = new StreamReader(fileName);
    return serializer.Deserialize(reader) as T;
}
codemeit
+3  A: 

You can also do this at the point of creating an XmlSerializer, by providing the additional details in the constructor. Note that it doesn't re-use such models, so you'd want to configure the XmlSerializer once (at app startup, from configuration), and re-use it repeatedly... note many more customizations are possible with the XmlAttributeOverrides overload...

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
static class Program
{
    static readonly XmlSerializer ser;
    static Program()
    {
        List<Type> extraTypes = new List<Type>();
        // TODO: read config, or use reflection to
        // look at all assemblies
        extraTypes.Add(typeof(Bar));
        ser = new XmlSerializer(typeof(Foo), extraTypes.ToArray());
    }
    static void Main()
    {
        Foo foo = new Bar();
        MemoryStream ms = new MemoryStream();
        ser.Serialize(ms, foo);
        ms.Position = 0;
        Foo clone = (Foo)ser.Deserialize(ms);
        Console.WriteLine(clone.GetType());
    }
}

public abstract class Foo { }
public class Bar : Foo {}
Marc Gravell
A: 

Marking the classes as Serializable and using SoapBinaryFormatter instead of XmlSerializer will give you this funtionality automatically. When serializing the type information of the instance being serialized will be written to the XML, and SoapBinaryFormatter can instantiate the subclasses when deserializing.

Jakob Christensen
That is officially obsolete: "Beginning with the .NET Framework version 3.5, this class is obsolete. Use BinaryFormatter instead."; http://msdn.microsoft.com/en-us/library/system.runtime.serialization.formatters.soap.soapformatter.aspx
Marc Gravell
I changed the answer accordingly. Thanks.
Jakob Christensen
I'm afraid BinaryFormatter produces no XML output, though. :-\
mafutrct
I know. Does it have to be XML? You can easily save binary data in databases and files.
Jakob Christensen
Yep, XML would be better :)
mafutrct
A: 

These links will probably be helpful to you:

I have a complex remoting project and wanted very tight control over the serialized XML. The server could receive objects that it had no idea how to deserialize and vice versa, so I needed a way to identify them quickly.

All of the .NET solutions I tried lacked the needed flexibility for my project.

I store an int attribute in the base xml to identify the type of object.

If I need to create a new object from xml I created a factory class that checks the type attribute then creates the appropriate derived class and feeds it the xml.

I did something like this (pulling this out of memory, so syntax may be a little off):

(1) Created an interface

interface ISerialize
{
    string ToXml();
    void FromXml(string xml);       
};

(2) Base class

public class Base : ISerialize
{
    public enum Type
    {
        Base,
        Derived
    };

    public Type m_type;

    public Base()
    {
        m_type = Type.Base;
    }

    public virtual string ToXml()
    {
        string xml;
        // Serialize class Base to XML
        return string;
     }

    public virtual void FromXml(string xml)
    {
        // Update object Base from xml
    }
};

(3) Derived class

public class Derived : Base, ISerialize
{
    public Derived()
    {
         m_type = Type.Derived;
    }

    public override virtual string ToXml()
    {
        string xml;
        // Serialize class Base to XML
        xml = base.ToXml();
        // Now serialize Derived to XML
        return string;
     }
     public override virtual void FromXml(string xml)
     {
         // Update object Base from xml
         base.FromXml(xml);
         // Update Derived from xml
     }
};

(4) Object factory

public ObjectFactory
{
    public static Base Create(string xml)
    {
        Base o = null;

        Base.Type t;

        // Extract Base.Type from xml

        switch(t)
        {
            case Base.Type.Derived:
                o = new Derived();
                o.FromXml(xml);
            break;
         }

        return o;
    }
};
Dana Holt