Наследование сериализации с использованием любопытно повторяющегося шаблона

У меня есть базовый класс, определенный следующим образом:

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);
        }
}
}

И классы наследуют это следующим образом:

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)
    {

    }
}

И это код, который вызывает функции Save:

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 выполняется без каких-либо проблем, но использование Save в MenuNavigationCommand дает мне эту ошибку:

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

Все, что мне нужно, чтобы сделать MenuNavigationCommand - это сохранить поля, существующие в классе Command, от которого он наследуется, а не новые поля в MenuNavigationCommand. Есть какой-либо способ сделать это? Или я должен просто реализовать метод Load and Save для каждого класса, который использует более одного уровня наследования?

РЕДАКТИРОВАТЬ: Добавлен полный источник файлов.

1 ответ

Решение

MenuChoiceCommand наследуется Command, который наследует XMLBackedObject<Command>не XMLBackedObject<MenuChoiceCommand>, Так что сериализатор создан Save для типа Commandне MenuChoiceCommand... вам нужно сделать MenuChoiceCommand унаследовать XMLBackedObject<MenuChoiceCommand> чтобы это сработало (но тогда вы не сможете заставить его наследовать Command, поскольку C# не допускает множественное наследование).

На первый взгляд, использование любопытно повторяющегося шаблона может показаться хорошей идеей, но, как вы видите, вы можете быстро столкнуться с его ограничениями.

В любом случае, я не думаю, что логика сериализации должна быть частью самого класса данных; вероятно, было бы лучше сделать это во вспомогательном классе с помощью универсальных методов:

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);
        }
    }
}
Другие вопросы по тегам