Friday, April 7, 2017

Normandy Would be Nokias Android mobile

Normandy Would be Nokias Android mobile


This isnt really a rant about Windows Mobile per se, but its an issue I seem to run into on a consistent basis: you cant deserialize an XmlAttribute into a struct. Consider the following code:

using System; using System.Linq; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Xml.Serialization; using System.IO; namespace SmartDeviceProject2 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { XmlSerializer ser = new XmlSerializer(typeof(Foo)); string xml = "<Foo Moo="2,3"/>"; MemoryStream mem = new MemoryStream(); StreamWriter writer = new StreamWriter(mem); writer.Write(xml); writer.Flush(); mem.Seek(0, SeekOrigin.Begin); Foo f = (Foo)ser.Deserialize(mem); } } public struct Bar : IFormattable { public int X; public int Y; public static Bar Parse(string s) { string[] strings = s.Split(,); Bar ret = new Bar(); ret.X = int.Parse(strings[0]); ret.Y = int.Parse(strings[1]); return ret; } public override string ToString() { return string.Format("{0},{1}", X, Y); } #region IFormattable Members public string ToString(string format, IFormatProvider formatProvider) { return ToString(); } #endregion } public struct Foo { [XmlAttribute] public Bar Moo; } }

Attempting to run this code on the desktop would fail on the line that instantiates the XmlSerializer: "Cannot serialize member Moo of type SmartDeviceProject2.Foo. XmlAttribute/XmlText cannot be used to encode complex types.".

However, on .NET CF 2.0, it fails on deserialization with the cryptic message: "The type SmartDeviceProject2.Bar was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically."

But my gripe is the following: I have a struct, it is IFormattable, and it can be Parsed (like int.Parse, DateTime.Parse, et al). Why is that not sufficient enough for serialization to and from an XmlAttribute string?

Anyways, here is the workaround:

  1. For all types that you want to be deserialized from attributes, explicitly give them the [XmlElement] attribute. Yes, you read that right.
  2. Create an XmlSerializer. Attach a new method to the UnknownAttribute event.
  3. Whenever this event is fired, use a combination of the attribute name and reflection to find the PropertyInfo/FieldInfo for what XmlSerializer cant deserialize. Then find that object types static Parse method, and call it on the attribute string. Then use that PropertyInfo/FieldInfo to set the property.

The new code would look something like this (you will need to add your own code to handle if it is a Property, since my sample is only looking for fields):

private void button1_Click(object sender, EventArgs e) { XmlSerializer ser = new XmlSerializer(typeof(Foo)); string xml = "<Foo Moo="2,3"/>"; MemoryStream mem = new MemoryStream(); StreamWriter writer = new StreamWriter(mem); writer.Write(xml); writer.Flush(); mem.Seek(0, SeekOrigin.Begin); ser.UnknownAttribute += new XmlAttributeEventHandler(ser_UnknownAttribute); Foo f = (Foo)ser.Deserialize(mem); } void ser_UnknownAttribute(object sender, XmlAttributeEventArgs e) { FieldInfo field = e.ObjectBeingDeserialized.GetType().GetField(e.Attr.Name); MethodInfo parse = field.FieldType.GetMethod("Parse"); field.SetValue(e.ObjectBeingDeserialized, parse.Invoke(null, new object[] { e.Attr.Value })); }
public struct Foo { [XmlElement] public Bar Moo; }

Unfortunately I have not figured out a similar workaround for serialization...


Go to link for download