The poo crew was having some trouble understanding the behavior of XML serialization on .NET. So we will add some clarity.
We wanted a serializer where default values could be set in code when reading older serialized XML and all tags were written regardless of “default value”. This way, a human could inspect the XML and know that the properties they see are all of them, at the time of serialization.
XML Serializer
XmlSerializer has been there since the beginning of .NET time (or at least 1.1). Here are some characteristics of XmlSerializer.
- All public properties from a public object are written using XmlSerializer as long as the [System.ComponentModel.DefaultValueAttribute(x)] is not attributed to the property. This also can look like [DefaultValue(x)] and it is the same attribute.
- For strings, include [XmlElement(IsNullable = true)] as a decorator if you want the tag specifically in the XML and the string is not assigned.
- The default construction is performed when deserializing. This is true for setting values in the constructor or assigning at declaration.
It is important to note that [DefaultValue()] has a second purpose. Property grids use this to know whether or not to bold a value in the UI. If the value = default value, the text is not bold. If value != default value, the text is bold. That is all it does. It absolutely does not change the class member no matter what your friends say.
Data Contract Serializer
This was added to the framework around .NET 3.0 (if we can believe Microsoft). Here are the high points:
- All properties with [DataMember] appear to be written regardless of default values are set or not. Why it didn’t work with SpiralToPolarPersistedData is something to look into.
- Constructors are not called when deserializing. This is true for setting values in constructors or assigning at declaration.
- The only way to guarantee a default value is to assign the values in a method decorated with [OnDeserializing]. A common pattern is to call the default method assigning from the constructor and from the OnDeserializing method (assuming the default method isn’t overrideable).
- If you do not include OnDeserializing, the values in the class are the type’s default values regardless of construction or declaration.
XML Serializer Example Code
using System; using System.IO; using System.Xml; using System.Xml.Serialization; public class Script { public class Record { private double n1; private double n2 = 100; private string operation; private double result; internal Record() { //n2 = 100; } internal Record(double n1, double n2, string operation, double result) { this.n1 = n1; this.n2 = n2; this.operation = operation; this.result = result; } public double OperandNumberOne { get { return n1; } set { n1 = value; } } public double OperandNumberTwo { get { return n2; } set { n2 = value; } } [XmlElement(IsNullable = true)] public string Operation { get { return operation; } set { operation = value; } } public double Result { get { return result; } set { result = value; } } public override string ToString() { return string.Format("Record: {0} {1} {2} = {3}", n1, operation, n2, result); } } static public void Main(string[] args) { Record record0 = new Record(); Console.WriteLine(record0.ToString()); Record record1 = new Record(1, 2, "+", 3); XmlSerializer serializer = new XmlSerializer(typeof(Record)); using (FileStream stream = File.Open("test.xml", FileMode.Create)) { serializer.Serialize(stream, record1); } Console.WriteLine("Press any key..."); Console.ReadKey(false); using (FileStream stream = File.Open("test.xml", FileMode.Open)) { Record record2 = (Record) serializer.Deserialize(stream); Console.WriteLine(record2.ToString()); } } }
Data Contract Serializer Example Code
using System; using System.Runtime.Serialization; using System.IO; using System.Xml; public class Script { [DataContract] internal class Record { private double n1; private double n2; // = 100; private string operation; private double result; internal Record() { // n2 = 100; */ SetDefaults(); } [OnDeserializing] private void OnDeserializing(StreamingContext context) { SetDefaults(); } private void SetDefaults() { n2 = 100; } internal Record(double n1, double n2, string operation, double result) { this.n1 = n1; this.n2 = n2; this.operation = operation; this.result = result; } [DataMember] internal double OperandNumberOne { get { return n1; } set { n1 = value; } } [DataMember] internal double OperandNumberTwo { get { return n2; } set { n2 = value; } } [DataMember] internal string Operation { get { return operation; } set { operation = value; } } [DataMember] internal double Result { get { return result; } set { result = value; } } public override string ToString() { return string.Format("Record: {0} {1} {2} = {3}", n1, operation, n2, result); } } static public void Main(string[] args) { Record record0 = new Record(); Console.WriteLine(record0.ToString()); Record record1 = new Record(1, 2, "+", 3); DataContractSerializer serializer = new DataContractSerializer(typeof(Record)); using (FileStream stream = File.Open("test.xml", FileMode.Create)) { serializer.WriteObject(stream, record1); } Console.WriteLine("Press any key..."); Console.ReadKey(false); using (FileStream stream = File.Open("test.xml", FileMode.Open)) { XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(stream, new XmlDictionaryReaderQuotas()); Record record2 = (Record) serializer.ReadObject(reader, true); Console.WriteLine(record2.ToString()); } } }