By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
428,683 Members | 1,597 Online
Bytes IT Community
Submit an Article
Got Smarts?
Share your bits of IT knowledge by writing an article on Bytes.

How To : Apply XmlSerialization with an Interface

100+
P: 179
This is a little bit more of an advanced topic for serialization. For those who don't know what XML serialization is, then this probably isn't for you just yet. For those that do, you may have come across the problem of serializing an interface, something that you simply can't do using the standard XmlSerialization techniques. This articles looks at how to do that, although it might be a little length. Lets take the following problem:

Expand|Select|Wrap|Line Numbers
  1.  public interface IPoint
  2.     {
  3.         double X;
  4.         double Y;
  5.         double DistanceBetween(IPoint point);
  6.     }
  7.  
  8.     public class Point2Dimensional : IPoint
  9.     {
  10.         public double X { get; protected set; }
  11.         public double Y { get; protected set; }
  12.         public virtual double DistanceBetween(IPoint point) { ... }
  13.     }
  14.  
  15.     public class Point3Dimensional : Point2Dimensional
  16.     {
  17.         public double Z { get; protected set; }
  18.         public override double  DistanceBetween(IPoint point) { ... }
  19.     }
  20.  
If we now have a class that has an IPoint.

Expand|Select|Wrap|Line Numbers
  1.     public class PointHolder
  2.     {
  3.         public IPoint Point { get; set; }
  4.     }
  5.  
This now means that we cannot deserialize the PointHolder class using standard serialization, because the serializer isn't clever enough to figure out the type it should be deserializing. However, it is possible to do so manually, using some custom serialization.

Firstly we want to make sure both our points can serialize / deserialize themselves. So we change our code slightly. I've ommitted the parameterless constructors, obviously you'll need to add those in.

1) Make the IPoint interface implement IXmlSerializable

Expand|Select|Wrap|Line Numbers
  1.  public interface IPoint : IXmlSerializable
  2.     {
  3.         double X;
  4.         double Y;
  5.         double DistanceBetween(IPoint point);
  6.     }
  7.  
2) Make the Point2Dimensional and Point3Dimensional implement the IXmlSerializable interface like so. I've just done the 2Dimensional class, obviously the other just adds Z to the list of things to serialize/deserialize.

Expand|Select|Wrap|Line Numbers
  1.    public class Point2Dimensional : IPoint
  2.     {
  3.         public double X { get; protected set; }
  4.         public double Y { get; protected set; }
  5.         public virtual double DistanceBetween(IPoint point) { ... }
  6.  
  7.         public System.Xml.Schema.XmlSchema GetSchema()
  8.         {
  9.             return null;
  10.         }
  11.  
  12.         public void ReadXml(System.Xml.XmlReader reader)
  13.         {
  14.             this.X = Convert.ToDouble(reader.GetAttribute("X"));
  15.             this.Y = Convert.ToDouble(reader.GetAttribute("Y"));
  16.         }
  17.  
  18.         public void WriteXml(System.Xml.XmlWriter writer)
  19.         {
  20.             writer.WriteAttributeString("X", this.X.ToString());
  21.             writer.WriteAttributeString("Y", this.Y.ToString());
  22.         }
  23.     }
  24.  
We now need to make our PointHolder implement IXmlSerializable too. This uses some extension methods which do the hardwork which we'll see in a minute. Again you'll need your parameterless constructor in there too.

Expand|Select|Wrap|Line Numbers
  1.     public class PointHolder : IXmlSerializable
  2.     {
  3.         public IPoint Point { get; set; }
  4.  
  5.         public System.Xml.Schema.XmlSchema GetSchema()
  6.         {
  7.             return null;
  8.         }
  9.  
  10.         public void ReadXml(System.Xml.XmlReader reader)
  11.         {
  12.             this.Point = reader.ReadXmlSerializableElement<IPoint>("Point");
  13.         }
  14.  
  15.         public void WriteXml(System.Xml.XmlWriter writer)
  16.         {
  17.             writer.SaveXmlSerialiableElement("Point", this.Point);
  18.         }
  19.     }
  20.  
What we do here, is basically pass in an element name, and either save or assign our Point property. Now lets take a look at the extension methods defined somewhere in a static class.

Expand|Select|Wrap|Line Numbers
  1. public static void SaveXmlSerialiableElement<T>(this XmlWriter writer, String elementName, T element) where T : IXmlSerializable
  2.         {
  3.             writer.WriteStartElement(elementName);
  4.             writer.WriteAttributeString("TYPE", element.GetType().ToString());
  5.             element.WriteXml(writer);
  6.             writer.WriteEndElement();
  7.         }
  8.  
What's going on here? Well we write a new element with the given name, we then save the full type and namespace of that element, call through to the WriteXml on the element itself, then write the end tag. So the XML will look something like:

<Point>
<Point3Dimensional TYPE="TestApp.Point3Dimensional" x="0", y="0", z="0">
</Point>

Not too difficult. Reading is where things get interesting. There's a couple of methods that I won't go into that you'll need, I'm sure you can figure them out. The code is here:

Expand|Select|Wrap|Line Numbers
  1.      public static void ReadToElement(this XmlReader reader, String element)
  2.         {
  3.             reader.ReadToNextNode();
  4.             while (reader.Name != element)
  5.             {
  6.                 reader.ReadToNextNode();
  7.             }
  8.         }
  9.  
  10.         public static void ReadToNextNode(this XmlReader reader)
  11.         {
  12.             // Read the current item to throw it away
  13.             reader.Read();
  14.  
  15.             // Keep on reading till we have read all the whitespace
  16.             while (reader.NodeType == XmlNodeType.Whitespace)
  17.             {
  18.                 reader.Read();
  19.             }
  20.         }
  21.  
The part that is really useful however is:

Expand|Select|Wrap|Line Numbers
  1.   public static T ReadXmlSerializableElement<T>(this XmlReader reader, String elementName) where T : IXmlSerializable
  2.         {
  3.             reader.ReadToElement(elementName);
  4.  
  5.             Type elementType = Type.GetType(reader.GetAttribute("TYPE"));
  6.             T element = (T)Activator.CreateInstance(elementType);
  7.             element.ReadXml(reader);
  8.             return element;
  9.         }
  10.  
What this does, is firstly locates the element of interest. Then we read out the type of the element and create a new instance of it. We can then call through to the ReadXml on the element to initalize all it's members and return it.

This then means we can use XmlSerialization on our PointHolder regardless of the type of IPoint that it is storing within the Point property.
Jul 6 '09 #1
Share this Article
Share on Google+