Click here to Skip to main content
Click here to Skip to main content

XML Serialization of a Class Inherited from Generic List of type Interface

, 12 Aug 2009
Rate this:
Please Sign up or sign in to vote.
An article on XML Serialization of a class inherited with a generic List of interface i.e. List

Introduction

The article describes how to serialize an object of a class inherited from generic list of interface (i.e. List<InterfaceName>) using XML serialization. I've developed the sample project in ASP.NET 3.5 but the same code can be used in .NET 2.0.

Here I'm assuming that you know the basic implementation of IXmlSerializable inteface. IXmlSerializable interface exposes three methods GetSchema(), ReadXML() and WriteXML(). We will return null from GetSchema() and add code to ReadXML() and WriteXML() function.

Background

There was a requirement to create a Workflow engine in one of the project I was working on. This workflow engine (object of type WorkFlow class) contains workflow items and excutes them one by one and then move to next.

Any item of workflow should contain Execute method and ExecutionCompleted event. So I created an interface named IWorkFlowItem which contains the Execute method and the ExecutionCompleted event plus several common properties. Initially, I required two workflow items i.e. Page and Condition. I created these two by implementing IWorkFlowItem interface. Then I created a class WorkFlowItems by inheriting generic List<IWorkFlowItem> and added a member of this type to the main Workflow class.

Everything worked fine till one requirement came to serialize the workflow object and save it. It threw me an error when I tried to serialize it. In the beginning I implemented the WorkFlowItems class with IXmlSerializer and put a dirty code to serialize the class in ReadXml() and WriteXml() methods. But, because the type of workflow items can increase to any extent this solution was of no use. Finally I've come with a solution and sharing it with you.

I couldn't put the actual code and putting here a different example but it will give you the exact picture of my implementation of interface serialization.

Why XML Serializer cannot serialize an interface?

Have a look at the definitions of the classes used for this example: 




Now have a look at the use of project class:  

The above serialization code will throw an error because IProjectMember is not an object, its an interface. An interface can only access members of the object by pointing to same location in memory and it is limited to access only those members which are in scope of its own. That say the IProjectMember interface can only access Name, TotalHoursWorked and Responsibilities properties of an object that implements it.

Again, if somehow, we were able to serialize the properties what IProjectMember exposes, we would have got a serialized xml something like this:

The problem with above serialization is:

  • We dont know what type of object an IProjectMember exposes. Is it a SoftwareDeveloper or a ProjectLeader?
  • We dont have additional properties specific to an object, that means while deserializing we don't have actual state of the object.
  • On top-of-all IProjectMember is an interface so you cannot create an instance of it. That means all the above points are null.

The Idea to Solve this Problem

We need a XML like given below to persist the actual state of the SoftwareProject object.

The bad thing about an interface is, you cannot create an object out of it, but the good thing is it points to an object (Instance of a class). Every object in .NET is, by default, inherited from object base class. I've always wondered why .NET has inherited every object from the base object class but it was a life saver for me in this case. The GetType() method of an object returns actual type of that object. We will see that how I've used this method to solve this problem in 'Understanding the Code' section.

Ok, enough story, have a look at the ideas to serialize and deserialize the object/xml.

To Serialize:

The idea is to get exact type of object to which an interface item is pointing while iterating the list. Say, for the first interface item in the Members list it is pointing to ProjectLeader object while the second object is pointing to SoftwareDeveloper object. After getting the type, cast that object to that type and serialize it and then write the serialized data on the xml writer stream in WriteXml() method.

To Deserialize:

To deserialize we need to convert the xml to an object. For this the ReadXML() method of IXMLSerializable interface provides xmlReader parser. We will iterate through the items of SoftwareProject node. Each child node name represents the actual class name. While iterating through items we can create a XMLSerializer to deserialize the data and can use Type.GetType(ClassName) to pass the type as input parameter to this XMLSerializer.

Understanding the Code

Files Description:

  • IProjectMember.cs - Interface defining common properties/methods for a project member. The generic list of this interface will be serizlied.
  • ProjectLeader.cs - Class to be used as a item for generic list to serialize.
  • SoftwareDeveloper.cs - Class to be used as a item for generic list to serialize.
  • ProjectTeam.cs - Class inherited from generic list of IProjectMember that is being serialized.
  • Project.cs - Class defining root object to hold ProjectTeam as a member to hold all the software developers and project leaders.
  • GenericListSerializer.cs - Class to xml serialize a generic list of type interface.
  • GenericListDeSerializer.cs - Class to xml deserialize a generic list of type interface.

Inteface to Define a Project Member

namespace SerializableList
{ 
	/// <summary>

	/// Interface to describe a project member 
	/// </summary> 
	public interface IProjectMember 
	{ 
		/// <summary>
		/// Name of the member 
		/// </summary>
		string Name { get; set; } 
		

		/// <summary> 
		/// Total number of hours the member has worked 
		/// </summary>
		int TotalHoursWorked { get; set; } 

		/// <summary> 
		/// Responsibilities the member has. 
		/// </summary> 
		string Responsibilities { get; set; } 
	} 
}

Project Team Class

namespace SerializableList
{ 
	public class ProjectTeam : List<IProjectMember>, IXmlSerializable 
	{ 
	
		#region IXmlSerializable Members 

		public XmlSchema GetSchema() 
		{ return null; } 

		public void ReadXml(XmlReader reader) 
		{ 
			GenericListDeSerializer<IProjectMember> dslzr =
                               new GenericListDeSerializer<IProjectMember>(); 
			dslzr.Deserialize(reader, this); 
		} 

		public void WriteXml(XmlWriter writer) 
		{ 
			GenericListSerializer<IProjectMember> serializers =
                               new GenericListSerializer<IProjectMember>(this); 
			serializers.Serialize(writer); 
		} 

		#endregion

	} 
}

To implement IXmlSerializable interface the class should implement these three methods

  1. GetSchema() - In current case I'm returning null;
  2. ReadXml() - This function is used to deserialize the object(converting xml to original object). In this function I've created an object of GenericListDeSerializer<IProjectMember> and passed the current reader stream and current object to its Deserialize(..) method.
  3. WriteXml() - This function is used to serialize the object(converting the original object to xml). In this function I've created an object of GenericListSerializer<IProjectMember> and then passing current writer stream to its Serialize(..) method

Serialization and Deserialization

To instantiate XmlSerializer object we require to pass a Type as a constructor parameter. Now, in current scenario because the list will contain different type of objects, we need to keep a list of every type of XmlSerializer, so that the list item can be serialized/deserialized with that specific XmlSerializer.

In sample application I've used Dictionary<key,value> to hold list of XmlSerializer. Here, type name is the  key and XmlSerializer object is the value. The GetSerializerByTypeName(string typeName) is used to find and add XmlSerializer object from this Dictionary<key, value> object.

Generic List Serializer

namespace SerializableList 
{ 

	/// <summary>
	/// Generic list serializer 
	/// </summary>
	public class GenericListSerializer<T> 
	{ 
		//To retain the serializer list that the T contains

		private List<T> _interfaceList; 

		//Serializers collection stored with type name
		private Dictionary<string, XmlSerializer> serializers; 

		/// <summary/>

		/// Creates a new instance of generic list serializer 
		/// </summary/>
		/// <param name="interfaceList">Generic list of interface type</param>
		public GenericListSerializer(List<T> interfaceList) 
		{ 
			_interfaceList = interfaceList; 
			InitializeSerializers(); 
		} 


		/// <summary>
		/// Initializes all the type of XML serializers the
                  /// generic list requires 
		/// </summary>
		private void InitializeSerializers() 
		{ 
			serializers = new Dictionary<string, XmlSerializer>(); 
			for (int index = 0; index < _interfaceList.Count; index++) 
			{ 

				//Add new serializer to the serializers list by
                                     //passing the class name 
				//Ignore if already added
				GetSerializerByTypeName(
                                        _interfaceList[index].GetType().FullName); 
			} 
		} 
		
		/// <summary>
		/// Serializes list 
		/// </summary>
		/// <param name="outputStream">Ouput stream to write the
                  /// serialized data</param>

		public void Serialize(XmlWriter outputStream) 
		{ 
			for (int index = 0; index < _interfaceList.Count; index++) 
			{ 
				//Get appropriate serializer

				GetSerializerByTypeName(
                                       _interfaceList[index].GetType().FullName).Serialize
				(outputStream, _interfaceList[index]); 
			} 
		} 

		/// <summary>
		/// Gets serializer by type name from internal XML serializers list 
		/// If specific serializers doesn't exists adds it and returns it 
		/// </summary>
		/// <param name="typeName">Class type name</param>

		/// <returns>XmlSerializer</returns>
		private XmlSerializer GetSerializerByTypeName(string typeName) 
		{ 
			XmlSerializer returnSerializer = null; 
			
			//Check if existing already

			if (serializers.ContainsKey(typeName)) 
			{ 
				returnSerializer = serializers[typeName]; 
			} 
			//If doesn't exist in list create a new one and add it to list
			if (returnSerializer == null) 
			{ 
				returnSerializer = new XmlSerializer(
                                         Type.GetType(typeName)); 
				serializers.Add(typeName, returnSerializer); 
			} 

			//Return the retrived XmlSerializer

			return returnSerializer; 
		} 
	} 
}

The GenericListSerializer<T> class exposes one constructor GenericListSerializer(List<T> interfaceList) and one public method - Serialize(XmlWriter outputStream).

GenericListSerializer(List<T> interfaceList) - The constructor accepts the generic list to be serialized as a input parameter and invokes InitializeSerializers() method to create a list of XmlSerializer.

Serialize(System.Xml.XmlWriter outputStream) - This is the main method which serializes the generic list when invoked. It iterate through each item of generic list, while iterating, it gets the class type name of current item with CurrentItem.GetType().Name method and passes this to GetSerializerByTypeName() method to retrive appropriate XmlSerializer object. After getting XmlSerializer it, then invokes Serialize method and passes main XmlWriter output stream and current item(of generic list) to serialize.

Generic list deserializer

namespace SerializableList 
{ 
	public class GenericListDeSerializer<t /> 
	{ 
		//Serializers collection stored with type name
		private Dictionary<string, XmlSerializer> serializers; 

		public GenericListDeSerializer() 
		{ 
			serializers = new Dictionary<string, XmlSerializer>(); 
		} 


		/// <summary>
		/// Deserializes list 
		/// </summary>
		/// <param name="outputStream">Ouput stream to write the
                  /// serialized data </param>
		public void Deserialize(XmlReader inputStream, List<T> interfaceList) 
		{ 
			//Get the base node name of generic list of items of
                           // type IProjectMember

			string parentNodeName = inputStream.Name; 

			//Move to first child
			inputStream.Read(); 

			while (parentNodeName != inputStream.Name) 
			{ 
				XmlSerializer slzr = GetSerializerByTypeName(
                                        inputStream.Name); 
				interfaceList.Add((T)slzr.Deserialize(inputStream)); 
			} 
		}
        
		/// <summary>

		/// Gets serializer by type name from internal XML serializers list 
		/// If specific serializers doesn't exists adds it and returns it 
		/// </summary>
		/// <param name="typeName">Class type name</param>
		/// <returns>XmlSerializer</returns> 

		private XmlSerializer GetSerializerByTypeName(string typeName) 
		{ 
			XmlSerializer returnSerializer = null; 
			
			//Check if existing already
			if (serializers.ContainsKey(typeName)) 
			{ 
				returnSerializer = serializers[typeName]; 
			} 
			//If doesn't exist in list create a new one and add it to list 
			if (returnSerializer == null) 
			{ 
				returnSerializer = new XmlSerializer(Type.GetType(
                                         Type.GetType(this.GetType().Namespace + "." + 
                                         typeName)); 
				serializers.Add(typeName, returnSerializer); 
			} 

			//Return the retrived XmlSerializer

			return returnSerializer; 
		} 
	} 
}

The GenericListDeSerializer<T> class exposes one public method - Deserialize(XmlReader inputStream, List<T> interfaceList).

Deserialize(System.Xml.XmlReader inputStream, List<T> interfaceList) - This method deserializes the serialized object. It accepts two input parameter, XmlReader input stream and the generic list object to which deserialized objects will be added.

When the main XmlSerializer pases XmlReader stream to ReadXml() method of ProjectTeam class, the reader's pointer is located at <Members> tag. It is the member property name of type ProjectTeam in SoftwareProject class. This acts as a root node for all items in generic list of type IProjectMember.

  1. parentNodeName variable is used to hold the name of root node so that the deserializer can deserialize all the items unitl it reaches to end node i.e. </Members>
  2. inputStream.Read(); forwards the reader position to first child node i.e. <ProjectLeader>
  3. while (parentNodeName != inputStream.Name) keep a track of the scope of <Members> node. When it reaches to end node i.e. </Members> this condition will return false and loop will be terminated.
    1. XmlSerializer slzr = GetSerializerByTypeName(inputStream.Name); fetches the appropriate XmlSerializer from stream by passing the class name as a key.
    2. interfaceList.Add((T)slzr.Deserialize(inputStream)); adds the deserialized object to the passed generic list. Here I'm casting the deserialized objec to the IProjectMember interface. When slzr.Deserialize(...) gets invoked, it deserializes the current node i.e. <ProjectLeader>/<SoftwareDeveloper> and advances the reader to the next child node.

Limitations

  1. There is no additional properties in ProjectTeam : List<IProjectMember> class, because for that we may need to handle the processing manually. But for now I've not focused on that.
  2. I've given all the sample code in this tutorial with the namespace. This is because the GenericListDeSerializer<T> class should be in same namespace otherwise it will throw an error while invoking Type.GetType(string typeName). This is because the GetType requires fully qualified assembly name in this case it is SerializableList.ProjectLeader or SerializableList.SoftwareDeveloper. So with the help of this.GetType().Namespace property and child node name <ProjectLeader>/<SoftwareDeveloper> fully qualified name is being passed to Type.GetType(..) method.
  3. I have used Type.GetType().FullName property to get fully quilified assembly name of the type because while serializing the object it throws an error if only class name is passed.

History

Version 1.0

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Hemant__Sharma
Technical Lead L & T Infotech
India India
No Biography provided

Comments and Discussions

 
Questionfor tree structure, i append a line like below Pinmemberjoo jinho20-Oct-11 22:07 
NewsRe: for tree structure, i append a line like below Pinmemberjohnowl15-May-12 8:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 12 Aug 2009
Article Copyright 2009 by Hemant__Sharma
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid