【问题标题】:XML Serialization - different result in .NET 4.0XML 序列化 - .NET 4.0 中的不同结果
【发布时间】:2013-07-25 02:22:57
【问题描述】:

请参阅下面的代码,该代码将 XML 写入文件,以归档一个包含 3 个对象列表的简单类。列表中的 3 个对象彼此递减,Base、Derived1、Derived2。我在序列化期间使用 XMLArrayItemAttributes 覆盖名称。这在 .NET 3.0 中运行良好,但现在在 .NET 4.0 中输出不同的结果。请参阅下面的输出,特别注意第二个后代项目 DerivedItem2。

有没有人对此有任何经验,以及我如何修复它以使其在 .NET 4.0 中像在 v3.5 中一样工作?

似乎我无法控制数组项被覆盖的顺序。它似乎不是将它们添加到 XMLArrayItems 的顺序。

编辑:我刚刚在框架版本 4.0 和 4.5 上使用 MONO 尝试了相同的示例,它适用于这些。这可能只是 Microsoft 框架版本的错误吗?

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using System.Xml.Serialization;
using System.Xml;
using System.Xml.Schema;
using System.IO;


namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        TestGroup g = new TestGroup();
        XmlSerializer s = new XmlSerializer(typeof(TestGroup), g.GetOverrides());
        TextWriter w = new StreamWriter("c:\\#\\test.xml");
        s.Serialize(w, g);
        w.Close();
    }
}


public class TestGroup
{
    public List<BaseItem> Items { get; set; }

    public TestGroup()
    {
        Items = new List<BaseItem>();
        BaseItem b = new BaseItem();
        b.BaseName = "Base Name";
        Items.Add(b);
        DerivedItem d1 = new DerivedItem();
        d1.BaseName = "D1";
        d1.DerivedName = "D1";
        Items.Add(d1);
        DerivedItem2 d2 = new DerivedItem2();
        d2.BaseName = "D2";
        //d2.DerivedName = "D2";
        d2.Derived2Name = "D2";
        Items.Add(d2);
    }


    public XmlAttributeOverrides GetOverrides()
    {
        XmlAttributes atts = new XmlAttributes();

        for (int i = 0; i < Items.Count; i++)
        {
            BaseItem b = Items[i];
            Type ItemType = b.GetType();

            XmlArrayItemAttribute ItemAtt = new XmlArrayItemAttribute();

            ItemAtt.ElementName = ItemType.Name;
            ItemAtt.Type = ItemType;
            atts.XmlArrayItems.Add(ItemAtt);
        }

        XmlAttributeOverrides attOvers = new XmlAttributeOverrides();
        attOvers.Add(typeof(TestGroup), "Items", atts);

        return attOvers;
    }

}
public class BaseItem : IXmlSerializable
{
    public string BaseName;

    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        // not required for example
    }

    public virtual void WriteXml(XmlWriter writer)
    {
        writer.WriteElementString("BaseName", this.BaseName);
    }
}
public class DerivedItem: BaseItem
{
    public string DerivedName;

    public override void WriteXml(XmlWriter writer)
    {
        base.WriteXml(writer);
        writer.WriteElementString("DerivedName", this.DerivedName);
    }
}
public class DerivedItem2: DerivedItem
{
    public string Derived2Name;

    public override void WriteXml(XmlWriter writer)
    {
        base.WriteXml(writer);
        writer.WriteElementString("Derived2Name", this.Derived2Name);
    }
}

输出原始文件(.NET 3.0):

<?xml version="1.0" encoding="utf-8"?>
<TestGroup xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Items>
    <BaseItem>
      <BaseName>Base Name</BaseName>
    </BaseItem>
    <DerivedItem>
      <BaseName>D1</BaseName>
      <DerivedName>D1</DerivedName>
    </DerivedItem>
    <DerivedItem2>
      <BaseName>D2</BaseName>
      <DerivedName />
      <Derived2Name>D2</Derived2Name>
    </DerivedItem2>
  </Items>
</TestGroup>

输出已更改(.NET 4.0):

<?xml version="1.0" encoding="utf-8"?>
<TestGroup xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Items>
    <BaseItem>
      <BaseName>Base Name</BaseName>
    </BaseItem>
    <DerivedItem>
      <BaseName>D1</BaseName>
      <DerivedName>D1</DerivedName>
    </DerivedItem>
    <DerivedItem> 
      <BaseName>D2</BaseName>
      <DerivedName />
      <Derived2Name>D2</Derived2Name>
    </DerivedItem>
  </Items>
</TestGroup>

更新:.NET 4.5 的输出

<?xml version="1.0" encoding="utf-8"?>
<TestGroup xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Items>
    <BaseItem>
      <BaseName>Base Name</BaseName>
    </BaseItem>
    <BaseItem>
      <BaseName>D1</BaseName>
      <DerivedName>D1</DerivedName>
    </BaseItem>
    <DerivedItem2>
      <BaseName>D2</BaseName>
      <DerivedName />
      <Derived2Name>D2</Derived2Name>
    </DerivedItem2>
  </Items>
</TestGroup>

更新: 打开 app.config 中的调试开关,引用自 http://msdn.microsoft.com/en-us/library/aa302290.aspx ,我发现序列化应用覆盖的顺序与我填充覆盖数组的顺序不同。有谁知道这个顺序是如何确定或覆盖的?

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.diagnostics>
        <switches>
            <add name="XmlSerialization.Compilation" value="4" />
        </switches>
    </system.diagnostics>
</configuration>

这给了我一个 c# 输出文件,它显示覆盖顺序不正确:

void Write2_TestGroup(string n, string ns, global::WindowsFormsApplication1.TestGroup o, bool isNullable, bool needType) {
    if ((object)o == null) {
        if (isNullable) WriteNullTagLiteral(n, ns);
        return;
    }
    if (!needType) {
        System.Type t = o.GetType();
        if (t == typeof(global::WindowsFormsApplication1.TestGroup)) {
        }
        else {
            throw CreateUnknownTypeException(o);
        }
    }
    WriteStartElement(n, ns, o, false, null);
    if (needType) WriteXsiType(@"TestGroup", @"");
    {
        global::System.Collections.Generic.List<global::WindowsFormsApplication1.BaseItem> a = (global::System.Collections.Generic.List<global::WindowsFormsApplication1.BaseItem>)((global::System.Collections.Generic.List<global::WindowsFormsApplication1.BaseItem>)o.@Items);
        if (a != null){
            WriteStartElement(@"Items", @"", null, false);
            for (int ia = 0; ia < ((System.Collections.ICollection)a).Count; ia++) {
                global::WindowsFormsApplication1.BaseItem ai = (global::WindowsFormsApplication1.BaseItem)a[ia];
                if ((object)(ai) != null){
                    if (ai is global::WindowsFormsApplication1.DerivedItem) {
                        WriteSerializable((System.Xml.Serialization.IXmlSerializable)((global::WindowsFormsApplication1.DerivedItem)ai), @"DerivedItem", @"", true, true);
                    }
                    else if (ai is global::WindowsFormsApplication1.BaseItem) {
                        WriteSerializable((System.Xml.Serialization.IXmlSerializable)((global::WindowsFormsApplication1.BaseItem)ai), @"BaseItem", @"", true, true);
                    }
                    else if (ai is global::WindowsFormsApplication1.DerivedItem2) {
                        WriteSerializable((System.Xml.Serialization.IXmlSerializable)((global::WindowsFormsApplication1.DerivedItem2)ai), @"DerivedItem2", @"", true, true);
                    }
                    else  if ((object)(ai) != null){
                        throw CreateUnknownTypeException(ai);
                    }
                }
            }
            WriteEndElement();
        }
    }
    WriteEndElement(o);
}

【问题讨论】:

  • 是的,这看起来很奇怪。你试过.net 4.5吗?根据我的想法,检查ai 的类型的顺序应该是从最专业到最通用的类​​型。其他任何事情都没有意义,因为它永远不会在封闭的 XML 中输出正确的类型名称,因此反序列化不会起作用。您是否尝试过反序列化生成的 XML?如果您不覆盖 WriteXml(),您会得到相同的行为吗?
  • 序列化器可能有一些挑剔的方面,例如您是否尝试过确保您的派生类都具有 ReadXml() 方法?
  • 感谢 cmets Rory。我已经尝试了一些包含/排除 ReadXml/WriteXml 的组合,它似乎仍然给出了相同的基本元素名称。还在旧模式下使用 .NET 4.5 进行了尝试,但没有但仍然没有区别。
  • 当你删除WriteXmlGetSchema并使用属性而不是GetOverrides()来控制序列化Items时,你能得到同样的症状吗?即只使用没有覆盖的默认序列化。如果是这样,那么它似乎是一个明显的 XmlSerializer 错误,因为它不可能从生成的 XML 中反序列化,并且您只需要使用 MS 提出错误。如果这不会产生问题,则将其从那里构建到您当前的复制代码并查看问题出现的位置。我怀疑GetOverrides() 中的某些东西是罪魁祸首。
  • 如果我删除了覆盖,那么项目列表中的所有项目都被标记为&lt;BaseItem&gt;,我希望列表被定义为List&lt;BaseItem&gt;。然后提供覆盖以将列表中的特定对象类型序列化为正确的类型。 WriteXML 仅用于写出正常工作的对象类型的属性。

标签: c# .net xml serialization xml-serialization


【解决方案1】:

好吧,帕特,

在 .Net4 中测试您的代码时,我设法重现了同样的问题,而不是更改为 .Net4.5...

在 .Net4.5 中,输出似乎与您为 .Net3 引用的相同

所以直接跳过 .Net4 而直接使用 .Net4.5

这个问题的原因在于框架中对象是如何在内存中构造的。 在 .net4 中,它们可能从“基础”到“派生”,而在 .Net3 和 .Net4.5 中,它们被从“派生”到“基础”持有(在我看来更正确,这是一个见仁见智的问题)。 更具体地说,我认为:

.Net4 框架将对象实例存储为基类型,并带有指向派生实例的指针。

.Net4.5/.Net3 框架将对象实例存储为派生类型,并带有指向基实例的指针。

在这两种情况下,在常规场景中使用对象时,您最终会得到相同的结果。

我记得读过垃圾收集在 .net4.5 中有一些改进,我相信这只是 MS 开发人员为优化性能而进行的更改的一部分。

在这两个测试中,我使用了相同版本的 XML Serializer (4.0)

【讨论】:

  • G.Y,对不起,我有一段时间没看这个问题了。感谢您的信息。根据我对原始问题的评论(8 月 12 日),我相当肯定我使用 .NET 4.5 进行了测试。我得回去重试。我会尽快回复您。
  • 我刚刚在 .NET 4.5 下运行了该应用程序,请查看对原始问题的编辑结果。虽然结果不同,但仍不固定。
猜你喜欢
  • 1970-01-01
  • 2016-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-09
  • 1970-01-01
相关资源
最近更新 更多