Click here to Skip to main content
Licence CPOL
First Posted 31 Oct 2007
Views 41,551
Downloads 573
Bookmarked 19 times

Generic XML Serialization in C#

By | 31 Oct 2007 | Article
An article on writing a class to disk and then reading it back again

What Does this Article Cover?

  • What is Serialization, what types are there and how is it used
  • Alternative methods for saving an application's settings to disk
  • Creating an abstract class to implement XML Serialization via inheritance
  • Using Reflection to enumerate and set a class's properties
  • How to overcome property types that cannot be serialized

Introduction

Serialization is the process of encoding a data structure into a byte-form and saving it on a storage medium. This basically means, saving an object (i.e. an instance of a class, or control) to disk. There are two different ways to serialize an object. The first is using Binary Serialization. This kind of encoding is not readable if opened in something like Notepad, the second type of encoding is XML Serialization. This method effectively writes the object to disk as an XML file. It isn't very often that you have to save a Button or other graphical control to disk but it is often common for applications to write other information to disk.

Probably the most common type of data written to disk is settings for an application. For example, if you open Notepad, change the default font and close the program, the next time you open it, the font will be how you left it. There are two main ways that an application can save its settings. The first is in the Windows Registry. The Registry is like a giant database of application and operating system settings stored on the local computer. Before the introduction of the Windows Registry, it was customary for applications to save their settings in INI files. I'm sure most of you would have seen files with the extension ".ini" before. These files are traditionally saved in a similar format to this:

; Lines with a semi colon indicate a comment
; For readability and navigation section titles are added
[Section 1]
; Settings are stored like this
Setting1=value1
Setting2=value2
 
[Section 2]
Variable1=value3
Variable2=value4

As you can probably guess, short of writing an extensive class, the only way to read an INI file is to import the text and navigate through it till you find the appropriate setting and then write the whole settings file back to disk having regenerated the entire text file.

This method was superceded by the Windows Registry with the introduction of Windows 95 but you will still see a lot of applications that use this method, and with good reason. The Windows Registry is only stored on the local machine and cannot be transferred except by way of ".reg" files which contain instructions for updating the Registry. INI files, despite being awkward to read and write are highly portable. With portable hard drives and other transportable storage media becoming ever more common, it is not unusual for users to install programs on a USB drive so they can use them on other computers. If the application they install relies on a Registry Key to work properly then the program will most likely crash, or not function as expected. So surely, the optimal method of saving settings would be a quick method that is not only portable, but easy to read the results back into the application? That is where XML Serialization plays its part. By defining a class named Settings and then adding each setting as a different property, you can then use XML Serialization to write the entire settings class to disk and then read it back, populating each property. The settings class can also have a Save() and a Load() method to make it easier to use.

Class Definitions and Properties

Before anything, check if you have each of the following using statements wherever they are necessary otherwise you will need to use the fully qualified declarations.

using System;
using System.IO;
using System.Xml;
using System.Drawing;
using System.Reflection;
using System.Xml.Serialization;

The first thing to do when creating an XML Serializable Class is to define the class; as this class will be intended for application settings, we will call it Settings. We will give the class a few settings of different data types to show how each one will be written to disk. Originally when I started designing this, I used an interface with a Save() and Load() method because I thought that I would need to make references to the actual class properties. After doing a bit of research about Reflection, I came across a way to loop through all of a class's properties and set them without actually knowing their name. This will also be covered later on. Instead of an interface, I decided to use an abstract class that contained both of the methods declared virtual.

We'll start with the abstract class. This class is used as a base to any class that wants to implement XML Serialization. When inherited from, it passes the Save() and Load() methods to the sub class. Now let's look at the definition. The code for the Save() and Load() methods will be excluded for the moment because there are other methods to explore.

public abstract class XmlSerializable
{
    public virtual void Save(string path)
    {
        // Code omitted
    }

    public virtual void Load(string path)
    {
        // Code omitted
    }
}

The next thing to declare is the actual settings class. This simply includes inheriting from XmlSerializable and then adding all the different properties. Here is an example:

public class Settings : XmlSerializable
{
    private int mSetting1;
    public int Setting1
    {
        get { return mSetting1; }
        set { mSetting1 = value; }
    }
    
    private string mSetting2 = "";
    public string mSetting2
    {
        get { return mSetting2; }
        set { mSetting2 = value; }
    }
}

Here are the two properties that we want to save, not much going on here really, just two standard property declarations of type int and string. Now that we've taken a look at the basic stuff, we can finally get down to the actual methods.

Save and Load Methods

Serializing a class is relatively simple when you know what to do, it also remains the same no matter what class you try to serialize. Here is the first method:

public virtual void Save(string path)
{
    StreamWriter w = new StreamWriter(path);
    XmlSerializer s = new XmlSerializer(this.GetType());
    s.Serialize(w, this);
    w.Close();
}

The code is pretty self explanatory but I'll run through it anyway. The first line declares a new StreamWriter which is used to actually write the file. The constructor accepts the path variable as the location to which to save the file.

The second line declares a new XmlSerializer object which is used to convert the file to XML format. Its constructor accepts the type of the object to serialize, in our case the type of the Settings class.

The third line, serializes and saves the class, using w as the file stream.

The fourth line closes the file and flushes the data. This makes sure that all the data has been written and that the file can be used again. It prevents file access errors.

Now, we get to look at the load method which you might find a little bit more complex:

public virtual void Load(string path)
{
    if (File.Exists(path))
    {
        StreamReader sr = new StreamReader(path);
        XmlTextReader xr = new XmlTextReader(sr);
        XmlSerializer xs = new XmlSerializer(this.GetType());
        object c;
        if (xs.CanDeserialize(xr))
        {
            c = xs.Deserialize(xr);
            Type t = this.GetType();
            PropertyInfo[] properties = t.GetProperties();
            foreach(PropertyInfo p in properties)
            {
                p.SetValue(this, p.GetValue(c, null), null);
            }
        }
        xr.Close();
        sr.Close();
    }
}

A check is run to make sure the file exists, otherwise the StreamReader will never be able to open the file. The StreamReader object is used to read the contents of the XML file. The XmlTextReader interprets the file as an XML document and formats it as appropriate. The XmlSerializer object used reflects the given type, making it able to interpret each XML node as a property. Because we want to make the Load() method generic, we use late binding to avoid using any definite types so we declare the variable c as an object. The next check makes sure that the file is actually deserializable, this again stops errors. If the file is valid, then we deserialize it and assign it to our object c.

The next part of the process involves reflection. Reflection, like the name suggests is the ability of an object to "observe" its own structure as well as modify its behaviour. We start with a Type object. This object's GetProperties() method is used to loop through the classes properties, each property is represented by a PropertyInfo object. This class contains the method SetValue() which can be used to set the property of a class's given instance.

Finally, we close both of the reader objects to avoid any file access errors that might occur next time we save or load the settings.

Problems with XML Serialization

Unfortunately not every data type is able to be serialized, a couple of these types include Font and Color. Probably the most efficient workaround is to define a struct object that contains all the properties you want to persist. For example, for a property of type Color you would define the following:

public struct SerializableColor
{    
    public int A;
    public int R;
    public int G;
    public int B;

    public SerializableColor(Color color)
    {
        this.A = color.A;
        this.R = color.R;
        this.G = color.G;
        this.B = color.B;
    }
}

Now, instead of using a property of type Color you could use one of type SerializableColor. Unfortunately this would mean performing a conversion at each end. To solve this problem, we need to add two extra methods, ToColor() and FromColor(). With these two methods added, the struct now looks like this:

public struct SerializableColor
{            
    public int A;
    public int R;
    public int G;
    public int B;

    public SerializableColor(Color color)
    {
        this.A = color.A;
        this.R = color.R;
        this.G = color.G;
        this.B = color.B;
    }

    public static SerializableColor FromColor(Color color)
    {
        return new SerializableColor(color);
    }

    public Color ToColor()
    {
        return Color.FromArgb(A, R, G, B);
    }
}

Unfortunately, although the type conversion between SerializableColor and Color is now easier, it still does not remove the problem entirely. There is however, something we can do to make it easier. There are attributes that can be assigned to properties to have them ignored during the serialization process. If we were to use this in our settings class, we would have to do this:

[XmlIgnore()]
public Color ColorProperty
{
    get { return mColorProperty.ToColor(); }
    set { mColorProperty = SerializableColor.FromColor(value); }
}

private SerializableColor mColorProperty;
[XmlElement("ColorProperty")]
public SerializableColor XmlColorProperty
{
    get { return mColorProperty; }
    set { mColorProperty = value; }
}

The XmlIgnore attribute stops the serializer from adding the property to the XML file that it creates. The XmlElement attribute assigns a name for the element that will be written to the XML file. If the name is not defined, then it will use the property name as default. Now let's look at how a SerializableFont struct would work:

public struct SerializableFont
{
    public float Size;
    public string Name;
    public FontStyle Style;

    public SerializableFont(Font font)
    {
        this.Size = font.Size;
        this.Name = font.Name;
        this.Style = font.Style;
    }

    public static SerializableFont FromFont(Font font)
    {
        return new SerializableFont(font);
    }

    public Font ToFont()
    {
        return new Font(Name, Size, Style);
    }
}

And then the properties:

private Font mFontProperty;
[XmlIgnore()]
public Font FontProperty
{
    get { return mFontProperty; }
    set { mFontProperty = value; }
}

[XmlElement("FontProperty")]
public SerializableFont XmlFontProperty
{
    get { return SerializableFont.FromFont(FontProperty); }
    set { mFontProperty = value.ToFont(); }
}

And that just about sums up everything I have to say on XML Serialization. If you download the source or demo, you will find a fully functional implementation of this method.

History

  • 31st October, 2007: Initial post

License

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

About the Author

Thomas R. Wolfe

Student

United Kingdom United Kingdom

Member

Hi, my name's Tom I'm a student in the UK and I'm 20 years old. I started programming when I was 11 with VB6 and now my primary language is C#. My other hobbies include Photoshop and playing the piano.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
SuggestionI hope you watch this , Tom Pinmemberdadrock5213:41 19 Apr '12  
GeneralRe: I hope you watch this , Tom Pinmemberdadrock524:29 20 Apr '12  
GeneralSnippet for XmlSerialisation Pinmembergrom le barbare6:10 9 Jan '08  

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.

Permalink | Advertise | Privacy | Mobile
Web03 | 2.5.120517.1 | Last Updated 31 Oct 2007
Article Copyright 2007 by Thomas R. Wolfe
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid