Serialization in C#

Can be used for complex datasaving (useful for OOP)

There are a lot of ways to save your data (ie. your World, Maps, Configurations, DataBases). One of them is Serialization. I’ll explain the usage of it with the .NET Framework 2.0 (and C#). It was possible in C++ before, but not that easy. Now you can achieve it with only a few lines of code.

What happens?

A class / structure will be converted to a stream. Streams for example can be saved in files, transmitted via TCP/IP and more and can contain every type of data (string, integer, arrays). This file can be read in and can be transformed back to class by Deserialization. Therefor you need to have that class / structure in your program, otherwise it will be a stream with useless information (you can read in of course with some tricks, but it wont be that easy).

How to do it?

The two methods

There are basically two types of serialization that can be used :

  • Xml Serialization
  • Binary Serialization

Differences

  • XML Serialization is referred to as “shallow“, which means that it doesn’t serialize the private, protected and internal members or the class; only the public members and/or public properties.
  • Contrarily, Binary Serialization (using the BinaryFormatter) is referred to as “deep" serialization. This means that all serializable members of the class will be serialized, unless the compiler is instructed otherwise with the [NonSerialized] attribute.
  • XML Serialization is more tolerant on versioning than Binary Serialization is. For example, if you change the signature (the members list for instance) of a class and try to deserialize an old version of that class with the BinaryFormatter, an exception will arise. For the XMLSerializer, if a node in an XML file is missing to the class definition, the field will just be set to null; if unknown node are found, you can hook to a serializer event to identify them. This allows for more specific versioning mechanisms.

1. XML Serialization

XML Serialization has one big advantage. You can edit the files without the program, as the output file can be opened with a simple text editor.

The requirements for a class to be XML-serializable :

  1. The class must be public;
  2. The class must have a public parameterless constructor, which the XmlSerializer can invoke when deserializing the class from an Xml file;
  3. All of its members which should be serialized must be public, or have public read/write properties;
  4. The class must contain elements that the XmlSerializer can serialize. See its MSDN entry for more info.

The following lines must be added to the beginning of your code:

using System.Xml.Serialization; // Xml Serialization Methods
using System.IO;                // File System Methods

This will enable you to use classes of these namespaces directly without writing the namespace structure before them.

public class MyWorld
{
 public MyWorld(){} // Constructor Function of MyWorld
 
 // Some Variables
 public int ID;
 public string Name;
 public string Music;
 
 // Here could be some arrays with our worldobjects
 // and their positions
}

Alternatively, you could put private fields with public properties, like so :

public class MyWorld
{
 public MyWorld(){} // Constructor Function of MyWorld
 
 // Some Variables
 private int id
 private string name;
 private string music;
 
 // Public properties
 public int ID 
 {
  get { return id; }
  set { id = value; }
 }
 
 public string Name
 {
  get { return name; }
  set { name = value; }
 }
 
 public string Music
 {
  get { return music; }
  set { music = value; }
 }
}

The rest of the tutorial stays the same, whatever method was used. The properties-using one is cleaner and is the one recommended for C# 2.0/2005. Custom attributes on properties can be used to format how the content will serialize as well. See this link for more information.

When our program starts, a world must be initialized. We write a function for that:

MyWorld TestWorld; // Now it is not a local variable of InitWorld, so we can access it from other functions too.
public void InitWorld()
{
 // Create a new object of type MyWorld
 TestWorld = new MyWorld();
 
 // Fill in some Values
 TestWorld.ID = 1;
 TestWorld.Name = "Ancient Underground World";
 TestWorld.Music = "mymusic.mp3";
}

But now we want to save our world. This is achieved with the following lines:

public void SaveWorld()
{
 // Create an serializer, which takes over the object-type MyWorld
 XmlSerializer serializer = new XmlSerializer(typeof(MyWorld));
 // Create an stream-object and save it as a file
 StreamWriter writer = File.CreateText(@"C:\MyWorld.xml");
 // This function does it all for us
 serializer.Serialize(writer, TestWorld);
 // Close the file
 writer.Close();
}

Now, in our main function (which is called when the Application is executed):

InitWorld();
SaveWorld();

A file named MyWorld.xml will be created in our C:\ root directory. It will contain the following data:

<?xml version="1.0" encoding="utf-8"?>
<MyWorld xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <ID>1</ID>
  <Name>Ancient Underground World</Name>
  <Music>mymusic.mp3</Music>
</MyWorld>

There you can see: All our data we added to the class before with InitWorld are saved in that XML file now. You can easily change them and if we load them in again they will contain your modified data. And here comes the function to read in our file.

public void LoadWorld()
{
 // Again, create a serializer, which takes over the object-type MyWorld
 XmlSerializer serializer = new XmlSerializer(typeof(MyWorld));
 // Create a stream-object and open the file
 StreamReader reader = File.OpenText(@"C:\MyWorld.xml");
 // This is the fuction which does all for us again.
 TestWorld = serializer.Deserialize(reader) as MyWorld;
}

Annotation: A serializer always return an Object. But TestWorld is an object of the type MyWorld. So with an “as” statement (or with a cast using (MyWorld)) the Object is casted to the right type.

2. Binary Serialization

One problem of the XML serialization is : everybody can see and edit your data. One avenue to prevent this would be to encrypt or encode the XML, but .Net provides another way of saving our data in a more complex and size-effective file which can’t be edited that easily. It’s called Binary Serialization and we only need a BinaryFormatter for that.

That said... To use it, first we have to modify our ‘usings’ :

//using System.Xml.Serialization;                     // Serialization Methods (XML)
using System.IO;                                      // File System Methods
using System.Runtime.Serialization.Formatters.Binary; // Serialization Methods (Binary)

Another thing must be added to the MyWorld class for it to be serializable using a BinaryFormatter : the [Serializable] attribute. This attribute tells all IFormatter implementors (like BinaryFormatter) that he’s allowed to serialize this class.

[Serializable]
public class MyWorld() 
{
  ...
}

Now we have to change our SaveWorld function...

public void SaveWorld()
{
 // Create a binary formatter
 IFormatter bformatterf = new BinaryFormatter();
 // Create a stream-object and save it as a file
 StreamWriter writer = File.CreateText(@"C:\MyWorld.dat");
 // This function does it all for us
 bformatter.Serialize(writer.BaseStream, database);
 // Close the file
 writer.Close();
}

...and of course our LoadWorld function.

public void LoadWorld()
{
 // Again, create a binary formatter
 IFormatter bformatter = new BinaryFormatter();
 // Create a stream-object and open the file
 StreamReader reader = File.OpenText(@"D:\MyWorld.dat");
 // This is the fuction which does all for us again.
 TestWorld = (MyWorld) bformatter.Deserialize(reader.BaseStream);
}

This was all! Now we can take a look into that created DAT file (with the editor):

 ����  ERuntime, Version=1.0.1936.29141, Culture=neutral, PublicKeyToken=null

... are the first few chars (Try it, i cant add the rest of the file as it contains controlcharacters like backspace etc and they are NOT good for the Wiki). You can see that it is not very useful anymore. Of course, with some work you are able to extract information out of that, but its not that easy anymore (especially for integers) and for now that should fit. Now you have your own file format done. Congratulations!

As a note : You must serialize and deserialize a binary-formatted file in the same Assembly name. A serialized piece of data caries its Assembly name, type and version, which will conflict if you try to load it in a different code base. This happens even if the data structure/class/variable is the same, as the Assembly is different. To work around this, you must implement ISerializable and forge the Assembly name.

Binary Serializer / Deserializer Snippet

Here’s an example of a ready-to-use serializer, along with its deserializing counterpart. Its usage is fairly simple:

MyWorld world = GetWorld();
bool serializationSuccessful = Data.Serializer.Serialize<MyWorld>(world, "worldsave.dat");
 
// ...
 
world = Data.Serializer.Deserialize<MyWorld>("worldsave.dat");
 

And here’s the code:

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
 
namespace Data
{
 
    internal static class Serializer
    {
 
        public static bool Serialize<T>(T data, string filePath)
        {
            bool error = false;
 
            // Using a file stream.			
            FileStream fileStream = new FileStream(filePath, FileMode.Create);
 
            // Construct a BinaryFormatter and use it to serialize the data to the stream.
            BinaryFormatter formatter = new BinaryFormatter();
            try
            {
                formatter.Serialize(fileStream, (T)data);
            }
            catch (SerializationException e)
            {
                Console.WriteLine("Serializer: failed to serialize \"{0}\". Reason: {1}", filePath, e.Message);
                error = true;
            }
            finally
            {
                fileStream.Close();
            }
 
            return !error;
        }
 
        public static T Deserialize<T>(string filePath)
        {
            T data = default(T);
 
            // If the file exists...
            if (File.Exists(filePath))
            {
                // Open the file containing the data that you want to deserialize.
                FileStream fileStream = new FileStream(filePath, FileMode.Open);
                try
                {
                    BinaryFormatter formatter = new BinaryFormatter();
 
                    // Deserializing data.
                    data = (T)formatter.Deserialize(fileStream);
 
                }
                catch (SerializationException e)
                {
                    Console.WriteLine("Serializer: failed to deserialize \"{0}\". Reason: {1}", filePath, e.Message);
                    return default(T);
                }
                finally
                {
                    fileStream.Close();
                }
 
            }
 
            return data;
 
        }
 
    }
 
}

EagleEye’s serialize/deserialize VB code... Feel free to use it if you’re a VB guy.

Imports System.IO
Imports System.Data
Imports System.Runtime.Serialization
Imports System.Runtime.Serialization.Formatters.Binary
 
Public Module DataUtils
 
    Public Function SerialData(ByVal Dta As Object) As Byte()
        Dim ms As New MemoryStream
        Dim formatter As IFormatter = New BinaryFormatter
        Try
            formatter.Serialize(ms, Dta)
            ms.Seek(0, SeekOrigin.Begin)
            Return ms.ToArray
        Catch ex As Exception
            Return ms.ToArray
        End Try
    End Function
 
    Public Function DeserialData(ByVal Bytes As Byte()) As Object
        Try
            Dim ms As New MemoryStream(Bytes)
            Dim Formatter As IFormatter = New BinaryFormatter
            Return Formatter.Deserialize(ms)
        Catch ex As Exception
            Return Nothing
        End Try
    End Function
 
    Public Function SerialDataXML(ByVal Dta As Object) As Byte()
        Dim Serializer As New Xml.Serialization.XmlSerializer(Dta.GetType)
        Dim MS As New MemoryStream
        Serializer.Serialize(MS, Dta)
        Return MS.ToArray
    End Function
 
    Public Function DeserialDataXML(ByVal Bytes As Byte(), ByVal ObjType As Type) As Object
        Dim Stream As New MemoryStream(Bytes)
        Dim Serializer As New Xml.Serialization.XmlSerializer(ObjType)
        Return Serializer.Deserialize(Stream)
    End Function
End Module
 
tutorialsarticlesandexamples/introduction_to_serialization.txt · Last modified: 2013/11/22 13:32