我拥有的是一个自定义设置文件,我使用XmlSerializer
. 我的对象定义中没有定义模式,也没有序列化标签,只是直接的对象序列化(尽管如果需要我会添加它们)。
我的问题是我需要向对象添加数据成员。如果我这样做,我知道旧的设置文件不会反序列化。
有没有办法为添加的成员指定默认值,或者如果它们从 XML 中丢失,是否有一些简单的方法可以忽略它们?
我拥有的是一个自定义设置文件,我使用XmlSerializer
. 我的对象定义中没有定义模式,也没有序列化标签,只是直接的对象序列化(尽管如果需要我会添加它们)。
我的问题是我需要向对象添加数据成员。如果我这样做,我知道旧的设置文件不会反序列化。
有没有办法为添加的成员指定默认值,或者如果它们从 XML 中丢失,是否有一些简单的方法可以忽略它们?
来自MSDN
最佳实践为确保正确的版本控制行为,在不同版本之间修改类型时请遵循以下规则:
添加新的序列化字段时,应用 OptionalFieldAttribute 属性。
从字段中删除 NonSerializedAttribute 属性(在以前的版本中不可序列化)时,应用 OptionalFieldAttribute 属性。
对于所有可选字段,使用序列化回调设置有意义的默认值,除非可以接受 0 或 null 作为默认值。
我试图模拟你的情况,在新版本的类中有名为 Element2 的新成员。将我的新成员初始化为“这是新成员”这是完整的证明
Test1假设您使用只有一个 Element1 的 Root 类的旧定义进行序列化
使用新定义的 Root Class 进行序列化和反序列化时的Test2
要以任何方式提供默认值来回答您的问题,您应该使用“OptionalField”
using System;
using System.Runtime.Serialization;
using System.IO;
public class Test
{
[Serializable]
public class Root
{
[OptionalField(VersionAdded = 2)] // As recommended by Microsoft
private string mElement2 = "This is new member";
public String Element1 { get; set; }
public String Element2 { get { return mElement2; } set { mElement2 = value; } }
}
public static void Main(string[] s)
{
Console.WriteLine("Testing serialized with old definition of Root ");
Console.WriteLine(" ");
Test_When_Original_Object_Was_Serialized_With_One_Element();
Console.WriteLine(" ");
Console.WriteLine("Testing serialized with new definition of Root ");
Console.WriteLine(" ");
Test_When_Original_Object_Was_Serialized_With_Two_Element();
Console.ReadLine();
}
private static void TestReadingObjects(string xml)
{
System.Xml.Serialization.XmlSerializer xmlSerializer =
new System.Xml.Serialization.XmlSerializer(typeof(Root));
System.IO.Stream stream = new MemoryStream();
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
Byte[] bytes = encoding.GetBytes(xml);
stream.Write(bytes, 0, bytes.Length);
stream.Position = 0;
Root r = (Root)xmlSerializer.Deserialize(stream);
Console.WriteLine(string.Format("Element 1 = {0}", r.Element1));
Console.WriteLine(string.Format("Element 2 = {0}", r.Element2 == null ? "Null" : r.Element2));
}
private static void Test_When_Original_Object_Was_Serialized_With_One_Element()
{
TestReadingObjects(@"<Root> <Element1>1</Element1> </Root>");
}
private static void Test_When_Original_Object_Was_Serialized_With_Two_Element()
{
TestReadingObjects(@"<Root> <Element1>1</Element1> <Element2>2</Element2> </Root>");
}
}
// 这里是输出
它应该反序列化就好了,它只会使用默认构造函数来初始化项目。所以它们将保持不变。
您需要使用自定义方法手动处理它并使用适当的属性 OnSerializing/OnSerialized/OnDeserializing/OnDeserialized 标记它们,并手动确定如何初始化值(如果可以完成)
http://msdn.microsoft.com/en-us/library/system.runtime.serialization.ondeserializingattribute.aspx http://msdn.microsoft.com/en-us/library/system.runtime.serialization.ondeserializedattribute.aspx http://msdn.microsoft.com/en-us/library/system.runtime.serialization.onserializedattribute.aspx http://msdn.microsoft.com/en-us/library/system.runtime.serialization.onserializingattribute.aspx
更好的方法是事先确定版本并使用策略模式进行反序列化。这并不总是可能的,所以在这种情况下使用我的建议。
更新:前面的答案只适用于二进制序列化。对于常规 XML,您可以使用此方法。
class Program
{
static void Main(string[] args)
{
Deserialize(@"..\..\v1.xml");
}
private static Model Deserialize(string file)
{
XDocument xdoc = XDocument.Load(file);
var verAtt = xdoc.Root.Attribute(XName.Get("Version"));
Model m = Deserialize<Model>(xdoc);
IModelLoader loader = null;
if (verAtt == null)
{
loader = GetLoader("1.0");
}
else
{
loader = GetLoader(verAtt.Value);
}
if (loader != null)
{
loader.Populate(ref m);
}
return m;
}
private static IModelLoader GetLoader(string version)
{
IModelLoader loader = null;
switch (version)
{
case "1.0":
{
loader = new ModelLoaderV1();
break;
}
case "2.0":
{
loader = new ModelLoaderV2();
break;
}
case "3.0": { break; } //Current
default: { throw new InvalidOperationException("Unhandled version"); }
}
return loader;
}
private static Model Deserialize<T>(XDocument doc) where T : Model
{
Model m = null;
using (XmlReader xr = doc.CreateReader())
{
XmlSerializer xs = new XmlSerializer(typeof(T));
m = (Model)xs.Deserialize(xr);
xr.Close();
}
return m;
}
}
public interface IModelLoader
{
void Populate(ref Model model);
}
public class ModelLoaderV1 : IModelLoader
{
public void Populate(ref Model model)
{
model.City = string.Empty;
model.Phone = "(000)-000-0000";
}
}
public class ModelLoaderV2 : IModelLoader
{
public void Populate(ref Model model)
{
model.Phone = "(000)-000-0000";
}
}
public class Model
{
[XmlAttribute(AttributeName = "Version")]
public string Version { get { return "3.0"; } set { } }
public string Name { get; set; } //V1, V2, V3
public string City { get; set; } //V2, V3
public string Phone { get; set; } //V3 only
}
根据您的要求,这可以简化为模型(或模型加载器)上的单个方法。
这也可以用于反序列化后的模型验证。
编辑:您可以使用以下代码进行序列化
private static void Serialize(Model model)
{
XmlSerializer xs = new XmlSerializer(typeof(Model));
FileStream f = File.Create(@"..\..\v1.xml");
xs.Serialize(f, model);
f.Close();
}
如果您遵循此模式,则相当简单:
这样,即使序列化快照是从以前版本的程序集生成的,您也可以成功反序列化以前的数据。
使用[System.ComponentModel.DefaultValueAttribute]
定义序列化的默认值。
来自MSDN的示例:
private bool myVal=false;
[DefaultValue(false)]
public bool MyProperty {
get {
return myVal;
}
set {
myVal=value;
}
}
因此,如果您 DeSerialize 并且未填充属性,它将使用 defaultValue 作为值,您可以使用旧 XML 生成新对象。
如果在新版本中删除了属性,那么通过 XMLSerialization 应该没有任何问题。(据我所知)
您可以使用ExtendedXmlSerializer。这个序列化器支持反序列化旧版本的 xml。这是一个反序列化旧版本xml的示例
您甚至可以从一个文件中读取对象的不同版本。
.NET 提供了很多用于序列化/反序列化和版本控制的功能。
1)用户DataContract/DataMember属性和DataContractSerializer
2)根据 MSDN,这些变化正在破坏
3) 当带有额外字段的类型被反序列化为带有缺失字段的类型时,额外信息被忽略。
4) 当一个缺少字段的类型被反序列化为一个带有额外字段的类型时,额外的字段保留其默认值,通常为零或空。
5) 考虑使用 IExtensibleDataObject 进行版本控制