14

我拥有的是一个自定义设置文件,我使用XmlSerializer. 我的对象定义中没有定义模式,也没有序列化标签,只是直接的对象序列化(尽管如果需要我会添加它们)。

我的问题是我需要向对象添加数据成员。如果我这样做,我知道旧的设置文件不会反序列化。

有没有办法为添加的成员指定默认值,或者如果它们从 XML 中丢失,是否有一些简单的方法可以忽略它们?

4

7 回答 7

7

来自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>");
  }
}

// 这里是输出在此处输入图像描述

于 2011-11-17T23:19:39.487 回答
5

它应该反序列化就好了,它只会使用默认构造函数来初始化项目。所以它们将保持不变。

于 2011-11-17T21:59:00.200 回答
4

您需要使用自定义方法手动处理它并使用适当的属性 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();
        }
于 2011-11-17T23:37:47.783 回答
3

如果您遵循此模式,则相当简单:

  • 通过实现 ISerializable 自己处理序列化/反序列化
  • 使用它来序列化对象的成员序列化版本号。
  • 在反序列化代码上,针对版本号运行 switch-case 语句。当您开始时,您将只有一个版本 - 初始反序列化代码。随着您的前进,您将在新的序列化快照中标记一个较新的版本号。
  • 对于对象的未来版本,始终保持现有反序列化代码不变,或对其进行修改以映射到您重命名/重构的成员,并且主要只是为新的序列化版本添加新的 case 语句。

这样,即使序列化快照是从以前版本的程序集生成的,您也可以成功反序列化以前的数据。

于 2011-11-17T23:33:42.507 回答
2

使用[System.ComponentModel.DefaultValueAttribute]定义序列化的默认值。

来自MSDN的示例:

private bool myVal=false;

[DefaultValue(false)]
 public bool MyProperty {
    get {
       return myVal;
    }
    set {
       myVal=value;
    }
 }

因此,如果您 DeSerialize 并且未填充属性,它将使用 defaultValue 作为值,您可以使用旧 XML 生成新对象。

如果在新版本中删除了属性,那么通过 XMLSerialization 应该没有任何问题。(据我所知)

于 2011-11-18T07:42:44.213 回答
1

您可以使用ExtendedXmlSerializer。这个序列化器支持反序列化旧版本的 xml。这是一个反序列化旧版本xml的示例

您甚至可以从一个文件中读取对象的不同版本。

于 2016-09-22T08:28:00.890 回答
0

.NET 提供了很多用于序列化/反序列化和版本控制的功能。

1)用户DataContract/DataMember属性和DataContractSerializer

2)根据 MSDN,这些变化正在破坏

  • 更改数据协定的名称或命名空间值。
  • 使用 DataMemberAttribute 的 Order 属性更改数据成员的顺序。
  • 重命名数据成员。
  • 更改数据成员的数据协定。

3) 当带有额外字段的类型被反序列化为带有缺失字段的类型时,额外信息被忽略。

4) 当一个缺少字段的类型被反序列化为一个带有额外字段的类型时,额外的字段保留其默认值,通常为零或空。

5) 考虑使用 IExtensibleDataObject 进行版本控制

于 2011-11-18T00:21:56.567 回答