【问题标题】:XML Deserialization of collection property with code defaults具有代码默认值的集合属性的 XML 反序列化
【发布时间】:2015-01-13 17:01:20
【问题描述】:

对于应用程序配置,我经常会创建一个配置类,其中包含应用程序的配置值,然后我将其反序列化为要使用的对象。配置对象通常数据绑定到用户界面控件,以便用户可以更改和保留配置。配置类通常具有分配给属性的默认值,因此始终存在默认配置。这运作良好。我最近遇到了一个情况,我有一个提供一些默认路径信息的字符串列表。我所看到的让我意识到我并不完全知道在将 XML 反序列化为对象期间如何填充对象属性。

所以我创建了一个简单的示例来展示这种行为。下面是一个简单的类,它有几个属性,这些属性有一些代码默认值。

[Serializable]
public class TestConfiguration
   {
      public String Name 
      { 
         get
         {
            return mName;
         }
         set
         {
            mName = value;
         }
      }private String mName = "Pete Sebeck";

  public List<String> Associates 
  { 
     get
     {
        return mAssociates;
     }
     set
     {
        mAssociates = value;
     }
  } private List<String> mAssociates = new List<string>() { "Jon", "Natalie" };

  public override String ToString()
  {
     StringBuilder buffer = new StringBuilder();
     buffer.AppendLine(String.Format("Name: {0}", Name));
     buffer.AppendLine("Associates:");
     foreach(String associate in mAssociates)
     {
        buffer.AppendLine(String.Format("\t{0}", associate));
     }
     return buffer.ToString();
  }
   }

这是一个创建新对象的 main,将对象的状态打印到控制台,将其序列化 (xml) 到文件,从该文件重构对象,然后再次将对象的状态打印到安慰。我所期望的是一个与被序列化的对象相匹配的对象。我得到的是默认对象,其中添加了序列化列表的内容。

  static void Main(string[] args)
  {
     // Create a default object
     TestConfiguration configuration = new TestConfiguration();
     Console.WriteLine(configuration.ToString());

     // Serialize the object
     XmlSerializer writer = new XmlSerializer(typeof(TestConfiguration));
     StreamWriter filewriter = new StreamWriter("TestConfiguration.xml");
     writer.Serialize(filewriter, configuration);
     filewriter.Close();

     // Now deserialize the xml into another object
     XmlSerializer reader = new XmlSerializer(typeof(TestConfiguration));
     StreamReader filereader = new StreamReader("TestConfiguration.xml");
     TestConfiguration deserializedconfiguration = (TestConfiguration)reader.Deserialize(filereader);
     filereader.Close();

     Console.WriteLine(deserializedconfiguration.ToString());

     Console.ReadLine();
      }

结果:

Name: Pete Sebeck
Associates:
        Jon
        Natalie

Name: Pete Sebeck
Associates:
        Jon
        Natalie
        Jon
        Natalie

我想我一直认为 List 属性会被设置而不是附加到。有没有人有指向集合的反序列化过程的指针?我现在显然知道正确的搜索词,因为我的尝试是空的。我看到其他帖子描述了我所看到的以及他们自己实现序列化的方法。我更多的是寻找一个指针来描述反序列化集合时会发生什么,以便我可以向自己解释我所看到的。

【问题讨论】:

    标签: c# xml serialization xmlserializer


    【解决方案1】:

    您说得对,许多序列化程序(尽管不是全部)都以这种方式工作。 Json.NET 确实如此,它的JsonConverter.ReadJson 方法实际上有一个Object existingValue 正是这种情况。

    我不知道有任何文件详细说明了这些实施细节。确定序列化程序是否在存在时使用预分配集合而不是无条件分配然后自行设置的最简单方法是使用ObservableCollection&lt;T&gt; 实际测试它并在更改时附加调试侦听器:

    [Serializable]
    [DataContract]
    public class TestConfiguration
    {
        [DataMember]
        public String Name { get { return mName; } set { mName = value; } }
    
        private String mName = "Pete Sebeck";
    
        [DataMember]
        public ObservableCollection<String> Associates
        {
            get
            {
                Debug.WriteLine(mAssociates == null ? "Associates gotten, null value" : "Associates gotten, count = " + mAssociates.Count.ToString());
                return mAssociates;
            }
            set
            {
                Debug.WriteLine(value == null ? "Associates set to a null value" : "Associates set, count = " + value.Count.ToString());
                RemoveListeners(mAssociates);
                mAssociates = AddListeners(value);
            }
        }
    
        private ObservableCollection<String> mAssociates = AddListeners(new ObservableCollection<string>() { "Jon", "Natalie" });
    
        public override String ToString()
        {
            StringBuilder buffer = new StringBuilder();
            buffer.AppendLine(String.Format("Name: {0}", Name));
            buffer.AppendLine("Associates:");
            foreach (String associate in mAssociates)
            {
                buffer.AppendLine(String.Format("\t{0}", associate));
            }
            return buffer.ToString();
        }
    
        static ObservableCollection<String> AddListeners(ObservableCollection<String> list)
        {
            if (list != null)
            {
                list.CollectionChanged -= list_CollectionChanged; // In case it was already there.
                list.CollectionChanged += list_CollectionChanged;
            }
            return list;
        }
    
        static ObservableCollection<String> RemoveListeners(ObservableCollection<String> list)
        {
            if (list != null)
            {
                list.CollectionChanged -= list_CollectionChanged; // In case it was already there.
            }
            return list;
        }
    
        public static ValueWrapper<bool> ShowDebugInformation = new ValueWrapper<bool>(false);
    
        static void list_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (!ShowDebugInformation)
                return;
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    Debug.WriteLine(string.Format("Added {0} items", e.NewItems.Count));
                    break;
                case NotifyCollectionChangedAction.Move:
                    Debug.WriteLine("Moved items");
                    break;
                case NotifyCollectionChangedAction.Remove:
                    Debug.WriteLine(string.Format("Removed {0} items", e.OldItems.Count));
                    break;
                case NotifyCollectionChangedAction.Replace:
                    Debug.WriteLine("Replaced items");
                    break;
                case NotifyCollectionChangedAction.Reset:
                    Debug.WriteLine("Reset collection");
                    break;
            }
        }
    }
    
    public static class TestTestConfiguration
    {
        public static void Test()
        {
            var test = new TestConfiguration();
    
            Debug.WriteLine("\nTesting Xmlserializer...");
            var xml = XmlSerializationHelper.GetXml(test);
            using (new SetValue<bool>(TestConfiguration.ShowDebugInformation, true))
            {
                var testFromXml = XmlSerializationHelper.LoadFromXML<TestConfiguration>(xml);
                Debug.WriteLine("XmlSerializer result: " + testFromXml.ToString());
            }
    
            Debug.WriteLine("\nTesting Json.NET...");
            var json = JsonConvert.SerializeObject(test, Formatting.Indented);
            using (new SetValue<bool>(TestConfiguration.ShowDebugInformation, true))
            {
                var testFromJson = JsonConvert.DeserializeObject<TestConfiguration>(json);
                Debug.WriteLine("Json.NET result: " + testFromJson.ToString());
            }
    
            Debug.WriteLine("\nTesting DataContractSerializer...");
            var contractXml = DataContractSerializerHelper.GetXml(test);
            using (new SetValue<bool>(TestConfiguration.ShowDebugInformation, true))
            {
                var testFromContractXml = DataContractSerializerHelper.LoadFromXML<TestConfiguration>(contractXml);
                Debug.WriteLine("DataContractSerializer result: " + testFromContractXml.ToString());
            }
    
            Debug.WriteLine("\nTesting BinaryFormatter...");
            var binary = BinaryFormatterHelper.ToBase64String(test);
            using (new SetValue<bool>(TestConfiguration.ShowDebugInformation, true))
            {
                var testFromBinary = BinaryFormatterHelper.FromBase64String<TestConfiguration>(binary);
                Debug.WriteLine("BinaryFormatter result: " + testFromBinary.ToString());
            }
    
            Debug.WriteLine("\nTesting JavaScriptSerializer...");
            var javaScript = new JavaScriptSerializer().Serialize(test);
            using (new SetValue<bool>(TestConfiguration.ShowDebugInformation, true))
            {
                var testFromJavaScript = new JavaScriptSerializer().Deserialize<TestConfiguration>(javaScript);
                Debug.WriteLine("JavaScriptSerializer result: " + testFromJavaScript.ToString());
            }
        }
    }
    

    我跑了上面的测试,发现:

    1. XmlSerializer 和 Json.NET 使用预先存在的集合(如果存在)。 (在 Json.NET 中,这可以通过将 JsonSerializerSettings.ObjectCreationHandling 设置为 Replace 来控制)
    2. JavaScriptSerializerBinaryFormatterDataContractSerializer 不这样做,总是自己分配集合。对于后两者,这并不奇怪,因为两者都是do not call default constructors,而是直接分配空内存。

    我不知道案例 1 中的序列化程序为什么会这样。也许他们的作者担心包含类可能想要在内部使用被反序列化的集合的子类,或者像我所做的那样将观察者附加到可观察的集合,因此决定尊重该设计?

    注意 - 对于所有序列化程序(可能除了 BinaryFormatter,我不确定),如果集合属性被明确声明为数组,那么序列化程序将自行分配数组并在完全填充后设置数组。这意味着arrays can always be used as proxy collections during serialization

    通过使用代理数组,可以保证你的集合在反序列化过程中被覆盖:

        [IgnoreDataMember]
        [XmlIgnore]
        [ScriptIgnore]
        public ObservableCollection<String> { get; set; } // Or List<string> or etc.
    
        [XmlArray("Associates")]
        [DataMember(Name="Associates")]
        public string[] AssociateArray
        {
            get
            {
                return (Associates == null ? null : Associates.ToArray());
            }
            set
            {
                if (Associates == null)
                    Associates = new ObservableCollection<string>();
                Associates.Clear();
                if (value != null)
                    foreach (var item in value)
                        Associates.Add(item);
            }
        }
    

    现在该集合仅包含先前已序列化的成员以及所有 5 个序列化程序。

    【讨论】:

      猜你喜欢
      • 2011-07-16
      • 1970-01-01
      • 1970-01-01
      • 2011-03-25
      • 1970-01-01
      • 2021-05-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多