Tizz
Tizz

Reputation: 840

Serialize/Deserialize a dynamic object

I have the following classes:

    public abstract class Animal
    {
        public Animal() { _myType = getAnimal(this.GetType().Name); }
        private dynamic _myType; 
        public dynamic myType { get { return _myType; } }
    }

    public class Cat : Animal
    {
        public Cat() : base() { }
    }

And its helper functions:

    public static T CreateAnimal<T>(string animal)
    {
        Type type = Type.GetType(typeof(Form1).FullName + "+" + animal);
        return (T)Activator.CreateInstance(type);
    }
    public static dynamic getAnimal(string name)
    {
        dynamic theAnimal = Activator.CreateInstance(MyAnimals); // Will default to 'Cat'
        FieldInfo fi = MyAnimals.GetField(name);
        int iEnum = (int)fi.GetValue(MyAnimals);
        return Enum.ToObject(MyAnimals, iEnum);
    }

It gets its 'myType' from the dynamically created enum 'MyAnimals':

    public static Type MyAnimals;

    public static void CreateAnimalEnum()
    {
        // Get the current application domain for the current thread.
        AppDomain currentDomain = AppDomain.CurrentDomain;

        // Create a dynamic assembly in the current application domain,  
        // and allow it to be executed and saved to disk.
        AssemblyName aName = new AssemblyName("TempAssembly");
        AssemblyBuilder ab = currentDomain.DefineDynamicAssembly(
            aName, AssemblyBuilderAccess.Run);

        // Define a dynamic module in "TempAssembly" assembly. For a single-
        // module assembly, the module has the same name as the assembly.
        ModuleBuilder mb = ab.DefineDynamicModule(aName.Name);

        // Define a public enumeration with an underlying type of Integer.
        EnumBuilder eb = mb.DefineEnum("MyAnimalType", TypeAttributes.Public, typeof(int));

        var types = new List<Type>();
        int Count = 0;
        foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
            try
            {
                types.AddRange(assembly.GetTypes().Where(x => x.IsSubclassOf(typeof(Animal))));
            }
            catch { }
        foreach (var type in types)
            eb.DefineLiteral(type.Name, Count++);

        // Create the type and save the assembly.
        MyAnimals = eb.CreateType();
    }

So now when I create a cat, I cannot serialize it. "InvalidOperationException: There was an error generating the XML document." I have tried using DynamicObject, and I found a dynamic helper class (https://gist.github.com/martinnormark/2574972) but that doesnt help when I want to serialize a Cat object when its encapsulated in another class.

    public static bool Save(Animal animal)
    {
        System.Xml.Serialization.XmlSerializer ListSer = new System.Xml.Serialization.XmlSerializer(typeof(Animal));
        System.IO.StreamWriter mywriter = new System.IO.StreamWriter(@"test.txt",  false);
        ListSer.Serialize(mywriter, animal);
        mywriter.Flush();
        mywriter.Close();
        return true;
    }

    public Form1()
    {
        InitializeComponent();
        GetEDIDeviceTypesEnums();

        Animal c = new Cat();
        Save(c);

        // This way fails too
        dynamic cat = CreateAnimal<Animal>("Cat");
        Save(cat);
    }

What am I missing in order to serialize a Cat?

Upvotes: 0

Views: 7160

Answers (1)

Sean Skelly
Sean Skelly

Reputation: 1324

The XMLSerializer needs to know ahead of time what types it can serialize; if you initialize an XMLSerializer on an abstract class, it will only know how to serialize that class and not anything inheriting it.

XMLSerializer has another constructor that allows you to input an array of extra types for it to use when trying to serialize. You can dynamically build that array of types from GetAssemblies (similar to what you did to build your custom MyAnimals Enum):

    public static bool Save(Animal animal)
    {
        var lListOfAnimals = (from lAssembly in AppDomain.CurrentDomain.GetAssemblies()
                         from lType in lAssembly.GetTypes()
                         where typeof(Animal).IsAssignableFrom(lType)
                         select lType).ToArray();

        System.Xml.Serialization.XmlSerializer ListSer = new System.Xml.Serialization.XmlSerializer(typeof(Animal), lListOfAnimals);

Code lifted straight from Yahoo Serious' answer in this thread. As Yahoo Serious mentions, if you call Save often, using Reflection in this way may create a performance hit, so you might cache the array of Animal types rather than rebuilding it every time you serialize.

Upvotes: 1

Related Questions