Наследование сериализации с использованием любопытно повторяющегося шаблона
У меня есть базовый класс, определенный следующим образом:
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);
}
}
}