Question

I have a base class defined as follows:

public abstract class XMLBackedObject<T> where T: XMLBackedObject<T>
{   
/// <summary>
/// Load the specified xml file and deserialize it.
/// </summary>
/// <param name='filePath'>
/// File path to load
/// </param>
public static T Load(string filePath)
{
    XmlSerializer serializer = new XmlSerializer(typeof(T));
        using(FileStream stream = new FileStream(filePath, FileMode.Open))
        {
            return serializer.Deserialize(stream) as T;
        }
}

/// <summary>
/// Save this instance to the specified file path
/// </summary>
/// <param name='filePath'>
/// File path to save to.
/// </param>
public void Save(string filePath)
{
    XmlSerializer serializer = new XmlSerializer(typeof(T));
        using(FileStream stream = new FileStream(filePath, FileMode.Create))
        {
            serializer.Serialize(stream, this);
        }
}
}

And classes inherit it as follows:

Config.cs:

using UnityEngine;
using System.Collections.Generic;

    [System.Serializable]
    public class Config : XMLBackedObject<Config>
    {
        public Config()
        {
        }

        public string WordDirectoryPath;
        public string CommandDirectoryPath;
    }

Command.cs:

using UnityEngine;
using System.Collections.Generic;

[System.Serializable]
public abstract class Command : XMLBackedObject<Command>
{
    //The word that triggers this command
    public Word Word;
    //The command's target
    public List<Word> Targets;
    //Minimum number of targets for the command to be valid
    public int RequiredTargets;

    //Message to send when bad targets are supplied
    public string BadTargetString;
    //Message to send when no target is supplied
    public string noTargetString;


    public Command(Word word, List<Word> targets,int requiredTargets)
    {
        Targets = targets;
        this.Word = word;
        this.RequiredTargets = requiredTargets;
    }

    public Command()
    {
        Targets = new List<Word>(); 
    }

    /// <summary>
    /// Execute the command on the supplied targets
    /// </summary>
    /// <param name='targets'>
    /// Targets to process
    /// </param>
    public abstract void Execute(IEnumerable<Word> targets);
}

MenuNavigationCommand.cs:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[System.Serializable]
public class MenuChoiceCommand : Command {

    public MenuChoiceCommand()
    {
    }

    public MenuChoiceCommand(Word word, List<Word> targets, int requiredTargets) : base(word,targets,requiredTargets)
    {
    }

    public override void Execute (System.Collections.Generic.IEnumerable<Word> targets)
    {

    }
}

And this is the code that calls the Save functions:

public void BuildTestXMLFiles()
    {
        Config config = new Config();
        config.CommandDirectoryPath = "commdirpath";
        config.WordDirectoryPath = "wordirparth";
        config.Save (Application.dataPath + "/testconfig.xml");

        MenuChoiceCommand command = new MenuChoiceCommand(word,new List<Word>(),2);
        command.Targets.Add (word);
        command.Save (Application.dataPath + "/testcommand.xml");
    }

Config's Save function executes without any hitches, but using Save on MenuNavigationCommand gives me this error:

InvalidOperationException: The type of the argument object 'MenuChoiceCommand' is not primitive.

All I need MenuNavigationCommand to do is save the fields that exist in the Command class it inherits from, not any new fields in MenuNavigationCommand. Is there any way to do this? Or should I just implement a Load and Save method on every class that uses more than one level of inheritance?

EDIT: Added the full source for the files.

Was it helpful?

Solution

MenuChoiceCommand inherits Command, which inherits XMLBackedObject<Command>, not XMLBackedObject<MenuChoiceCommand>. So the serializer created by Save is for type Command, not MenuChoiceCommand... You would need to make MenuChoiceCommand inherit XMLBackedObject<MenuChoiceCommand> for this to work (but then you wouldn't be able to make it inherit Command, since C# doesn't allow multiple inheritance).

Using the curiously recurring template pattern for this might seem a good idea at first glance, but as you can see, you can quickly encounter its limitations.

Anyway, I don't think the serialization logic should be part of the data class itself; it would probably be better to do it in a helper class with generic methods:

public static class XmlHelper
{
    public static T Load<T>(string filePath)
    {
        XmlSerializer serializer = new XmlSerializer(typeof(T));
        using(FileStream stream = new FileStream(filePath, FileMode.Open))
        {
            return (T)serializer.Deserialize(stream);
        }
    }

    public static void Save<T>(T obj, string filePath)
    {
        XmlSerializer serializer = new XmlSerializer(typeof(T));
        using(FileStream stream = new FileStream(filePath, FileMode.Create))
        {
            serializer.Serialize(stream, obj);
        }
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top