使用 XML 序列化加载对象的多个版本
本文关键字:版本 对象 XML 序列化 加载 使用 | 更新日期: 2023-09-27 18:36:39
我得到了一个项目,在这个项目中,我将设计一种"通用"的方式来存储存储值并根据需要修改它们(在正在运行的程序之外)。这些值用于测试目的(即.max以及最小电压和电流阈值)。理想情况下,我也应该能够在多个程序和项目中共享这些价值观。
过去,我们会为此目的定义自己的脚本文件(通常是某种形式的 CSV 文件),不用说,它既混乱又低效。
现在,当给出这个项目时,我立即想到的是使用对象序列化(特别是XML序列化,因为它是人类可读的)。我们的测试如此多样化,以至于真的没有办法想出一个"通用"格式;因此,下一个最好的办法是自动化格式化。它允许我在程序输入时反序列化对象,然后在完成它时再次序列化它。最重要的是,我可以进入生成的XML文件并手动修改其内容。另外,我可以像使用常规对象一样使用数据。因此,我开始将所有这些值存储在对象中并对其进行序列化。
但是,我们的测试参数经常更改;有时我们可能会添加或删除测试用例等等。不幸的是,为了做到这一点,我需要修改我的对象。现在我只剩下同一对象的不同版本,我需要支持所有这些版本(或至少将数据向上转换为对象类型的最新版本)。
当然,一种解决方案是为每个版本使用继承,但这会变得混乱,而且这不是一种自然的编码方式(尤其是在测试硬件时)。反射可能是另一种选择,但我认为这将涉及手动解析器(我仍然需要支持多个版本)。我将不胜感激任何关于这里采取的最佳方法的意见。谢谢
请注意,我已使用 DataContractSerializer
来处理序列化和反序列化。
下面是我的代码如何工作的示例类;
主要功能:
namespace Test
{
internal class Program
{
private static void Main(string[] args)
{
SerializableSingleton<Class1>.initInstance("class1Singleton.xml");
Class1 obj1 = SerializableSingleton<Class1>.Instance;
Class1 obj2 = new SerializableInstance<Class1>("class1Instance.xml").Data;
}
}
}
要序列化的类:
namespace Test
{
[DataContract()]
class Class1 : AbstractSerializeableObject
{
protected override XMLVersion ObjectVersion
{
get { return version; }
}
XMLVersion version = new XMLVersion(1, 0, 0, "Init Version");
[DataMember(Name = "First Number")]
int number = 1;
[DataMember()]
string str = "hello";
[DataMember(Name = "Nested Class")]
NestedClass1 nClass = new NestedClass1();
[DataContract()]
class NestedClass1 :AbstractSerializeableObject
{
protected override XMLVersion ObjectVersion
{
get { return version; }
}
XMLVersion version = new XMLVersion(1, 0, 0, "Init Version");
[DataMember(Name = "Second Number")]
int number = 2;
[DataMember()]
string str = "world";
}
}
}
生成的 XML 文件:
类1单例.xml
<?xml version="1.0" encoding="utf-8"?>
<Class1 xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Test">
<First_x0020_Number>1</First_x0020_Number>
<Nested_x0020_Class>
<Second_x0020_Number>2</Second_x0020_Number>
<str>world</str>
</Nested_x0020_Class>
<str>hello</str>
</Class1>
类1实例.xml
<?xml version="1.0" encoding="utf-8"?>
<Class1 xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Test">
<First_x0020_Number>1</First_x0020_Number>
<Nested_x0020_Class>
<Second_x0020_Number>2</Second_x0020_Number>
<str>world</str>
</Nested_x0020_Class>
<str>hello</str>
</Class1>
请注意,此处的所有内容都按原样显示;生成的 XML 文件(到目前为止)没有问题。这些是我期望的结果。单例和实例的东西有很多层,这并不是真正重要的。重要的是序列化本身。重要的部分是序列化本身和版本控制。
以下是我进行序列化和反序列化的方法:
namespace XMLSerializationLib
{
/// <summary>
/// The XmlSerializer is used solely to encapulate the serialization and
/// deserialization of objects. It uses generics so most objects can make use
/// of this library, provided that they heed to the warning(s) below:
///
/// -- The object to be serialized MUST have a default, non-parameterized
/// contructor
///
/// </summary>
/// <typeparam name="T"> The object type to be serialized/deserialized </typeparam>
internal static class XmlSerializer<T>
{
/* ========================================================================= */
/* Functions */
/* ========================================================================= */
/// <summary>
/// Serializes an object of type T into an XML file
/// which can be edited outside of the program.
/// </summary>
/// <param name="obj"> The object which should be serialized </param>
/// <param name="path"> The path of the XML file to be generated </param>
internal static void Serialize(T obj, string path)
{
if (!(obj is AbstractSerializeableObject))
throw new SerializerTypeException("Serialized Object must inherit AbstractSerializeableObject");
// Make XML readable
var settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = "'t";
#if _NET_4_5_
// Enable serialization of `readonly` properties and types
System.Runtime.Serialization.SerializeReadOnlyTypes = true;
#endif
// Write to XML
using (var writer = XmlWriter.Create(path, settings))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
serializer.WriteObject(writer, obj);
writer.Close();
}
}
/// <summary>
/// Deserializes a provided XML file and stores the retrieved data inside
/// an object of type T.
/// </summary>
/// <param name="path"> The path of the XML file to be loaded </param>
/// <returns>
/// An filled object of type T; if the provided XML cannot be
/// deserialized, then it returns a T object with its default
/// properties.
/// </returns>
internal static T Deserialize(string path)
{
T obj;
DataContractSerializer deserializer = new DataContractSerializer(typeof(T));
XmlReader reader = new XmlTextReader(path);
#if _NET_4_5_
// Enable deserialization of `readonly` properties and types
System.Runtime.Serialization.SerializeReadOnlyTypes = true;
#endif
if (deserializer.IsStartObject(reader))
obj = (T)deserializer.ReadObject(reader);
else
obj = Activator.CreateInstance<T>();
reader.Close();
if (!(obj is AbstractSerializeableObject))
throw new SerializerTypeException("Serialized Object must inherit AbstractSerializeableObject");
return obj;
}
/// <summary>
/// Exception to be thrown if the provided object T does not inherit
/// AbstractSerializeableObject.
/// </summary>
internal class SerializerTypeException : Exception
{
/// <summary>
/// Exception constructor
/// </summary>
/// <param name="message"> Error message </param>
public SerializerTypeException(string message)
: base(message)
{
}
}
}
}
现在的问题如下:假设我要获取我的Class1
对象并将number
字段修改为等于382000
例如。然后我要完全删除str
字段,并添加一个名为 dbl
的新字段,该字段的值为 pi
。生成的类将如下所示:
修改后的类 1
namespace Test
{
[DataContract()]
class Class1 : AbstractSerializeableObject
{
protected override XMLVersion ObjectVersion
{
get { return version; }
}
XMLVersion version = new XMLVersion(1, 0, 0, "Init Version");
[DataMember(Name = "First Number")]
int number = 382000;
[DataMember()]
double dbl = 3.14;
[DataMember(Name = "Nested Class")]
NestedClass1 nClass = new NestedClass1();
[DataContract()]
class NestedClass1 :AbstractSerializeableObject
{
protected override XMLVersion ObjectVersion
{
get { return version; }
}
XMLVersion version = new XMLVersion(1, 0, 0, "Init Version");
[DataMember(Name = "Second Number")]
int number = 2;
[DataMember()]
string str = "world";
}
}
}
现在假设我在一台机器(机器 1)上运行旧版本的程序,在一台机器(机器 1)上使用原始版本的 Class1
,在另一台机器(机器 3)上使用新Class1
运行更新版本。为了论证起见,假设我在第三台机器(Machine2)上运行了一些中间版本的Class1
。现在我希望能够获取机器 1 生成的 XML 文件并快进它,以便兼容的数据能够在机器 2 和机器 3 上工作;当然,所有新的数据字段都将采用一些默认值。
在类似的情况下,我使用控制台应用程序将旧的xml更新为新结构。为此,我写了我对IDataContractSurrogate的实现:
public class SerializerTypeSurrogate : IDataContractSurrogate
{
public virtual Type GetDataContractType(Type type)
{
return type == typeof(Charge) ? typeof(OldCharge) : type;
}
public object GetObjectToSerialize(object obj, Type targetType)
{
return obj;
}
public virtual object GetDeserializedObject(Object obj, Type targetType)
{
var oldClass1= obj as OldClass1;
return oldClass1 != null ? new Class1(oldClass1) : obj;
}
public Type GetReferencedTypeOnImport(string typeName,
string typeNamespace, object customData)
{
return null;
}
public System.CodeDom.CodeTypeDeclaration ProcessImportedType(
System.CodeDom.CodeTypeDeclaration typeDeclaration,
System.CodeDom.CodeCompileUnit compileUnit)
{
return typeDeclaration;
}
public object GetCustomDataToExport(Type clrType, Type dataContractType)
{
return null;
}
public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
{
return null;
}
public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
{
}
}
您应该暂时保留旧类和新类,新类应该具有旧类对象的构造函数,但是您可以在以新形式转换所有旧 xml 后将其删除。
要从旧形式转换为新形式,您应该使用下一个代码:
var oldentity = Deserialize<T>(oldXml,
new SerializerTypeSurrogate());
var newXml = FormalizedChangeRecord.Serialize<T>(card);
public static TCard Deserialize<T>(string xmlData, IDataContractSurrogate surrogate) {
var serializer = new DataContractSerializer(typeof (T), new Type[0], 10000, true, true, surrogate);
using (var stringReader = new StringReader(changeRecord.CardData))
{
using (var xmlReader = XmlReader.Create(stringReader, new XmlReaderSettings { CloseInput = false }))
{
var entity= (TCard)serializer.ReadObject(xmlReader);
return entity;
}
}
}
public static string Serialize<T>(T entity)
{
var serializer = new DataContractSerializer(typeof (T));
using (var stringWriter = new StringWriter())
{
using (
var xmlWriter = XmlWriter.Create(stringWriter,
new XmlWriterSettings
{
CloseOutput = false,
Encoding = new UTF8Encoding(false),
OmitXmlDeclaration = true,
Indent = true
}))
{
serializer.WriteObject(xmlWriter, entity);
}
return stringWriter.ToString();
}
}