Please see the code below that writes XML out to file a simple class containing a list of 3 objects. The 3 objects in the list descend from each other, Base, Derived1, Derived2. I use XMLArrayItemAttributes to override the names during Serialization. This works fine in .NET 3.0, but now outputs a different result in .NET 4.0. Please see the below Outputs, noting specifically the second descendant item DerivedItem2.
Has anyone any experience with this and how I might fix it to work in .NET 4.0 as it did in v3.5?
It seems that I cannot control the order in which the array items are overridden. It doesn't appear to be the order in which they are added to the XMLArrayItems.
Edit: I've just tried the same example using MONO against framework versions 4.0 and 4.5 and it works fine with those. Could this just be a bug with the Microsoft framework versions?
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); } }
Output Original (.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>
Output Changed (.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>
Update: Output from .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>
Update: Turning on the debugging switch in the app.config as below, referenced from http://msdn.microsoft.com/en-us/library/aa302290.aspx , I find that the order in which the Serialization applies the overrides is different to the order in which I fill the override array. Anyone any idea how this order is determined or overridden?
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.diagnostics> <switches> <add name="XmlSerialization.Compilation" value="4" /> </switches> </system.diagnostics> </configuration>
This gives me a c# output file which shows the override order as incorrect:
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); }
Well Pat,
I have managed to reproduce the same problem when testing your code in .Net4 and than changing to .Net4.5...
In .Net4.5 the output seems the same as what you quoted for .Net3
So just go ahead and skip .Net4 and instead just use .Net4.5
Reason to this problem is originated to how objects are constructed in memory in the frameworks. In .net4 they probably being held from "base" to "derived" and in .Net3 and .Net4.5 they are held (more correctly in my opinion and it is a matter of opinion) from "derived" to "base". Being more specific on that, I believe that in:
.Net4 the framework stores the object instance as type of base with pointer to derived instance.
.Net4.5/.Net3 the framework stores the object instance as type of derived with pointers to base instance.
In both cases you end up getting the same result when working with the object in regular scenarios.
I do remember reading that garbage collecting had some improvements in .net4.5 and I believe this is just part of the things the MS devs changed to optimize the performance.
In both tests I worked with the same version of XML Serializer (4.0)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With