DCSIMG
Serializing with .NET 2.0 Generics (C# & VB) - .NET Geek

.NET Geek

"It is upon the Trunk that a gentleman works" - Confucius

Serializing with .NET 2.0 Generics (C# & VB)

On a project we're working on here at Renaissance we were in the need of serialization en mass. A lot of classes had to be serializable. Following are the requirements, the stages towards a solution and a proposed solution. See a better solution? Please let me know.

 

Requirements:

  1. It must be simple for the application programmer.
  2. The serialization output should be xml.
  3. Future changes in the serialization code must not involve making changes to client code. Eg. We may have to encrypt the serialized object representation.

 

Since this was my first date with serialization I naturally went googling to get a feeling for whom I'm dating. It quickly seemed that serialization wasn't that hard. Many people suggested using the following wherever you need to serialize.

 

      Dim serializedObj As String

      Dim serializer As New XmlSerializer(Me.GetType)

      Dim writer As New StringWriter

      serializer.Serialize(writer, Me)

      serializedObj = writer.ToString()

 

Not so bad. You decorate your class with the <Serializable()> attribute and maybe hide a few public properties with XmlIgnore and there you go.

 

It was immediately ruled out to have the client code contain this code in every class. So what about a utility method that will serialize/deserialize an object?

 

There are a few challenges here. The utility function must be able to handle any type of object. Until .NET 2.0 the answer would probably have been something along:

 

   Public Shared Function Serialize(obj as Object) As String

 

That looks OK. Lets have a look at the deserialize method as well and see if there are any surprises hiding there. In order to Deserialize we need another piece of information that we had when serializing. We need to know what type to deserialize to. The following signature should give us enough information to get the job done.

 

   Public Shared Function Deserialize(xml As String, obj As Object) As Object

 

The client code would then look something like

      ' serialize

      Dim instance1 As New TestClass

      Dim serializedInstance As String = SharedLib.Serialize(instance1)

 

      ' deserialize

      Dim instance2 As New TestClass

      instance2 = CType(SharedLib.DeSerialize(serializedInstance, instance2), _

            TestClass)

 

At this point I was in doubt. Should I stop here and accept something that I didn't feel very good about, or should I spend some more time to try to find a better solution. I had two problems with the above code. The client code was not intuitive enough and there was still too much code needed to get the job done. From an object oriented perspective I would much more like to have the methods on the class itself.

I decided to look for something else.

 

I hear you shout Generics, so here it comes, but with a twist.

We'll leave the concept of utility functions all together. In a normal object model you have objects that contain state and do actions. Most of us don't write helperLib.Drive(myCar), but rather myCar.Drive(). How can we apply that to the serialization problem at hand?

 

Lets first look at how we want the calling code to look like and then on how to implement our serialization to match that.

 

      ' serialize

      Dim instance1 As New TestClass

      Dim serializedInstance As String = instance1.Serialize()

 

      ' deserialize

      Dim instance2 As TestClass1

      instance2 = TestClass1.Deserialize(serializedInstance)

 

That's what I want the calling code to look like.

 

Just to make things even between VB.NET and C#, I'll do the remaining part in C#.

 

We want to add a Serialize() and Deserialize() method to any class that needs to be serialized. In order to minimize the coding effort of the classes that will use this functionality we will write a generic base class with the implementation of the two methods.

 

Here is the base class implementation.

 

   [Serializable()]

   public abstract class ContractBase<T>

   {

      /// <summary>

      /// Must have default constructor for xml serialization

      /// </summary>

      public ContractBase()

      {

      }

 

      /// <summary>

      /// Create an xml representation of this instance

      /// </summary>

      /// <returns></returns>

      public string Serialize()

      {

         XmlSerializer serializer = new XmlSerializer(this.GetType());

         using (StringWriter stream = new StringWriter())

         {

            serializer.Serialize(stream, this);

            stream.Flush();

            return stream.ToString();

         }

      }

 

      /// <summary>

      /// Creata a new instance from an xml string.

      /// The client is responsible for deserialization of the correct type

      /// </summary>

      /// <param name="xml"></param>

      /// <returns>new object of type T</returns>

      public static T Deserialize(string xml)

      {

         if (string.IsNullOrEmpty(xml))

         {

            throw new ArgumentNullException("xml");

         }

 

         XmlSerializer serializer = new XmlSerializer(typeof(T));

         using (StringReader stream = new StringReader(xml))

         {

            try

            {

               return (T)serializer.Deserialize(stream);

            }

            catch (Exception ex)

            {

               // The serialization error messages are cryptic at best.

               // Give a hint at what happened

               throw new InvalidOperationException("Failed to create object from xml string", ex);

            }

         }

      }

     

   }

 

Here is a sample class using the serialization base class we just wrote.

 

   [Serializable()]

   public class TestClass : ContractBase< TestClass >

   {

      private string m_firstName;

 

      public string FirstName

      {

         get

         {

            return m_firstName;

         }

         set

         {

            if (m_firstName == value)

               return;

            m_firstName = value;

         }

      }

 

Note that the TestClass inherits from a generic base class passing in its own type as the generic type.

 

The calling code now looks like what we wanted.

 

   //serialize

   TestClass instance1 = new TestClass();

   string serializedInstance = instance1.Serialize();

 

   //deserialize

   TestClass instance2;

   instance2 = TestClass.Deserialize(serializedInstance);
Posted: May 23 2006, 08:22 AM by Kim | with 11 comment(s) |
תגים:

Comments

Dusty said:

Kim,

Thanks so much for the great article! A while back, I too was looking for a solution to this same problem. I had settled for using generic parameters (which you can read about here: http://www.dustyd.net/archive/2006/05/03/Great-Blog-The-Joy-Of-Code.aspx )

I was very happy to stumble upon your article on Code Project :-) Thanks again!

Dusty Davidson
# June 2, 2006 8:06 AM

Jackie Goldstein's Weblog (In Israel) said:

Well, it took a little bit of prodding, but Kim Major is (finally!) blogging.&amp;nbsp; Kim is an amazing...
# June 22, 2006 11:44 PM

Big Dubb said:

Great article.  Just a thought tho...

If we truly want to encapsulate the functionality of the serialization and deserialization wouldn't we be better implementing this funcationality through an interface and an object that provides the function. Then including that class in the object that use it (e.g. Strategy pattern from GOF)?

Right now it's just an idea, and i'm playing with the implementation but if I get success i'll be more than happy to share a code example.

bigdubb_at_gmail_dot_com

# January 24, 2007 11:39 PM

jake said:

Can you apply this agains ObjectCollection instead of individual objects?

# June 7, 2007 11:32 PM

Kim said:

What you can do or not is mostly determined by the XmlSerializer. There is no difficulty in serializing a collection of objects. You have Class A that inherits from ContractBase. Class A contains a collection of objects of Class B. Now when you do a .Serialize() the object graph will be serialized. In this example, both Class A and Class B needs to be decorated with the Serializable attribute. Just as a side note, you could replace the usage of the XmlSerializer with another serializer if you find the XmlSerializer to limiting.

# June 8, 2007 8:25 AM

jake said:

Another curious question, can you take advantage of generics, collection and xmlserializer, one time you pass in order collection and next time you pass in customer collectection and in both cases it serialize and deserialize.

# June 8, 2007 4:50 PM

Kim said:

If I understand you correctly, you should be able to serialize/deserialize any class that inherits from ContractBase as long as it can be serialized within the limitations of the XmlSerializer.

I haven't tried this, but you should be able to do the following:

Pseudo code:

Class A (takes generic type T) Inherits ContractBase(Class A)

Class A can be serialized using the syntax: m_classA.Serialize() becase it inherits from ContractBase. In addition Class A contains some generic data T that was part of the declaration of Class A. So I guess that should be possible. I don't know if the XmlSerializer will get confused, but anyhow let me know if you try.

# June 8, 2007 5:49 PM

Kim said:

I just tried, and it doesn't work.

The XmlSerializer cannot serialize generic types. In order to achieve this you will have to do the serialization yourself or use a serializer that supports generic types.

# June 8, 2007 6:15 PM

jake said:

Yeah, you are right, i found out u can only deserialize one object back out even if the xml file contains more one object.

# June 9, 2007 12:22 AM

Mohamed Faramawi said:

Do you think we can apply this in a more generic solution that can handel serializing objects that are not marked as serializable.

what i was successfull with is, building a generic utility to serialize non serializable objects, but what im stuck with is, if these objects contains other objects that are not serialized, they can't get serialized easily.

contact me on faramawi@hubspot.com

# March 9, 2008 6:06 PM

Steve said:

I have been fighting with this for days.  Thank you so much for finally supplying a useable & understandable solution.  I wish I didn't have to use a C# to VB converter, but nothings perfect.  Here is the VB version of the ContractBase class:

Imports Microsoft.VisualBasic

Imports System.Web

Imports System.Xml

Imports System.Data

Imports System.Data.SqlClient

Imports System.Xml.Serialization

Imports System.IO

Imports System.Text

<Serializable()> _

Public MustInherit Class ContractBase(Of T)

   Public Sub New()

   End Sub

   ' <summary>

   ' Create an xml representation of this instance

   ' </summary>

   ' <returns></returns>

   Public Function Serialize() As String

       Dim serializer As New XmlSerializer(Me.[GetType]())

       Using stream As New StringWriter()

           serializer.Serialize(stream, Me)

           stream.Flush()

           Return stream.ToString()

       End Using

   End Function

   ' <summary>

   ' Create a new instance from an xml string.

   ' The client is responsible for deserialization of the correct type

   ' </summary>

   ' <param name="xml"></param>

   ' <returns>new object of type T</returns>

   Public Shared Function Deserialize(ByVal xml As String) As T

       Dim serializer As New XmlSerializer(GetType(T))

       If String.IsNullOrEmpty(xml) Then

           Throw New ArgumentNullException("xml")

       End If

       Using stream As New StringReader(xml)

           Try

               Return DirectCast(serializer.Deserialize(stream), T)

           Catch ex As Exception

               ' The serialization error messages are cryptic at best.

               ' Give a hint at what happened

               Throw New InvalidOperationException("Failed to create object from xml string", ex)

           End Try

       End Using

   End Function

End Class

# May 8, 2008 7:40 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 


Enter the numbers above: