c# - 使用代码默认值对集合属性进行 XML 反序列化

标签 c# xml serialization xmlserializer

对于应用程序配置,我经常会为应用程序创建一个包含配置值的配置类,然后将其反序列化为一个对象以供使用。配置对象通常数据绑定(bind)到用户界面控件,以便用户可以更改和保留配置。配置类通常具有分配给属性的默认值,因此始终存在默认配置。这很有效。我最近遇到一种情况,我有一个提供一些默认路径信息的字符串列表。我所看到的让我意识到我并不完全知道在对象的 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 属性将被设置而不是附加到。有没有人知道集合的反序列化过程?我显然现在知道正确的搜索词,因为我的尝试都是空的。我看到其他帖子描述了我所看到的以及他们自己实现序列化的方法。我更想寻找一个指针来描述当集合被反序列化时会发生什么,这样我就可以向自己解释我所看到的。

最佳答案

你是对的,许多序列化器(虽然不是全部)都是这样工作的。 Json.NET 确实如此,它的 JsonConverter.ReadJson 方法实际上有一个 Object existingValue正是针对这种情况。

我不知道有任何文档详细说明了此类实现细节。确定序列化程序是否在存在时使用预分配集合而不是无条件分配然后自行设置的最简单方法是使用 ObservableCollection<T> 对其进行实际测试。并在更改时附加调试监听器:

[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. JavaScriptSerializer , BinaryFormatterDataContractSerializer不要,并且总是自己分配集合。对于后两者,这并不奇怪,因为 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 个序列化器。

关于c# - 使用代码默认值对集合属性进行 XML 反序列化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27927496/

相关文章:

xml - 架构文件名未检测到命名空间

c# - 将 JSON 转换为对象,然后根据对象类型执行不同的操作

json - Unity 从服务器反序列化

c# - 在 Entity Framework Core 中复制整行

c# - 从对象数组中删除重复项

c++ - Xerces。将 DOMNode 指针动态转换为 DOMElement 返回 nullptr

xml - 使用 xslt 更新 XML 文件

python - 使用 Python 创建嵌套的 JSON 请求

c# - 为什么调用 DatabaseInstance.ExecuteSprocAccessor<T>(...) 对于这样一个简单的查询要花这么长时间?

c# - 队列消息未移至有害队列