XML序列化 - .NET 4.0中的不同结果

22
请查看下面的代码,它将一个包含3个对象列表的简单类写入XML文件。列表中的3个对象是从彼此派生的:Base、Derived1和Derived2。我使用XMLArrayItemAttributes在序列化期间覆盖名称。这在.NET 3.0中运行良好,但在.NET 4.0中输出了不同的结果。请参见下面的输出,特别注意第二个后代项DerivedItem2。
有没有人对此有经验,并知道如何在.NET 4.0中修复它,使其像v3.5一样工作?
似乎我无法控制数组项被覆盖的顺序。它似乎不是它们添加到XMLArrayItems中的顺序。
编辑:我刚刚尝试使用MONO针对框架版本4.0和4.5执行相同的示例,它们可以正常工作。这只是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(),是否会出现相同的行为? - Rory
序列化器可能有一些棘手的方面,例如您是否尝试确保所有派生类都具有ReadXml()方法? - Rory
感谢您的评论,Rory。我尝试了几种包括/排除ReadXml/WriteXml的组合,但似乎仍然会给出相同的基本元素名称。我还尝试了在.NET 4.5中使用传统模式和非传统模式,但仍然没有任何区别。 - Pat Buxton
当您删除WriteXmlGetSchema并改用属性来控制序列化Items时,是否会出现相同的症状?即仅使用默认序列化而没有覆盖。如果是这样,那么它似乎是一个明显的XmlSerializer错误,因为无法从生成的XML反序列化,您只能向MS报告错误。如果这不会产生问题,则从那里构建到您当前的复制代码,并查看问题出现的位置。我怀疑GetOverrides()中的某些内容是罪魁祸首。 - Rory
如果我删除覆盖,则项列表中的所有项都标记为“<BaseItem>”,这是我所期望的,因为该列表被定义为“List<BaseItem>”。然后提供覆盖以将列表中的特定对象类型序列化为正确的类型。WriteXML仅用于正确写出对象类型的属性。 - Pat Buxton
1个回答

5

嗯,Pat,

我已经成功地在.Net4中测试了您的代码,然后切换到.Net4.5,复现了相同的问题...

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

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

这个问题的原因源于框架中对象在内存中的构造方式。 在.Net4中,它们可能从“基本”到“派生”进行保存;而在.Net3和.Net4.5中,它们被保存(在我看来更正确,这是一个观点问题)从“派生”到“基本”。 更具体地说,在:

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

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

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

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

在两个测试中,我都使用了相同版本的XML序列化器(4.0)


G.Y,抱歉我有一段时间没有看这个问题了。感谢您提供的信息。我相当确定我按照原始问题上的评论(8月12日)使用了.NET 4.5进行测试。我将不得不回去重新尝试一下。我会尽快回复您。 - Pat Buxton
我刚刚在.NET 4.5下运行了该应用程序,请查看对原始问题的编辑以获取结果。虽然结果不同,但仍未解决。 - Pat Buxton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接