Achieve Persistence Through Serialization

AID: 4275
  • Status: Published

7140 points

  • Bytrestan
  • TypeTutorial
  • Posted on2010-12-21 at 14:44:16
Awards
  • Community Pick
  • Experts Exchange Approved

Summary:


Persistence is the capability of an application to store the state of objects and recover it when necessary. This article compares the two common types of serialization in aspects of data access, readability, and runtime cost. A ready-to-use code snippet using BinaryFormatter with simple encryption is provided.


Introduction:


I was amazed the first time I read the dotnet documentation about serialization. Prior to the dotnet era, it was a big headache to deal with configuration data. You would have to write large code pieces to stream the data out to a file and then parse the long strings again to find out the proper data to read back. When playing with serialization, I was hoping to create a complete cache of the application and restore it just like nowadays Windows system “Hibernation” feature. Although the reality is always far from the imagination, dotnet serialization is still very useful in caching “part” of an application – the data objects.

Dotnet framework provides two types of serialization: shallow serialization, and deep serialization, represented by
   XmlSerializer in System.Xml.Serialization namespace and
   BinaryFormatter in System.Runtime.Serialization.Formatters.Binary namespace,
respectively. The differences between the two types are obvious: the former is designed to save and load objects in human-readable XML format, and the latter provides compact binary encoding either for storage or for network streaming. The dotnet framework also includes the abstract FORMATTERS class that can be used as a base class for custom formatters. We will focus on XmlSerializer and BinaryFormatter in this article.


XmlSerializer Basics


There are three projects in the attached package. The first one XMLSerializerSample shows some typical scenario that XmlSerializer could be applied. In file SampleClasses.cs, three sample classes are defined:

    BuildinType contains properties with primary types;
    DerivedClass uses build-in reference types, also demonstrates a class with base class;
    CollectionTypes declares several different build-in Collection types.

The Main program routine simply serializes out the instance of each class to a file and reads it back sequentially, plus an array object to test the performance on bulk data. I tag the test case numbers in both the source code and the article.  You can perform the tests yourself if you'd like.  Simple guidelines are in the source code, which illustrate the basic elements of a Software Test Document (STD).

The output of the program is like:
test2.xml (Test Case 1):
<?xml version="1.0"?>
<DerivedClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <InstanceID>2</InstanceID>
  <Number>300.900024</Number>
  <Description>This is a test.</Description>
  <TestState>DONE</TestState>
  <TestTime>2010-12-08T02:23:50.265625+08:00</TestTime>
  <StrFont>Times New Roman, 10pt</StrFont>
</DerivedClass>

                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:

Select allOpen in new window



XmlSerializer supports:

  • All the primary types (Test Case 2);
  • Derived class (Test Case 3);
  • Simple collection types such as array, list (Test Case 4);
  • Public data members – only (Test Case 5).

The limitations are:

  • Most build-in reference types are not serializable (Test Case 6);
  • Static data member won’t get serialized (Test Case 7);
  • Private fields can not be saved (Test Case 5);
  • There must be a default constructor. Normally the complier will generate one if none explicit constructor is present. But sometimes we could create a parameterized constructor but forget to add a default constructor. Then the serialization would be “accidentally” disabled. (Test Case 8);
  • String manipulation is very expensive, and storage in text format is huge (Test Case 9):
demo1.PNG
  • 15 KB
  • Execution time and output file size
Execution time and output file size

The workaround for making a build-in type serializable (Test Case 10):
Font thisFont = new Font("Times New Roman", 10F);
        [XmlIgnore]
        public Font ThisFont        //Accessors for general calling. 
        {
            get { return thisFont; }
            set { thisFont = value; }
        }

        public string StrFont       //Accessors for serialization.
        {
            get { return Utility.ObjectToString(thisFont); }
            set { thisFont = (Font)Utility.ObjectFromString(typeof(Font), value); }
        }

                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:

Select allOpen in new window



Overall, the biggest advantage of XmlSerializer is the human-readable format of the output. If you have a relatively simple object and need to modify the data directly, XmlSerializer is a good choice.


BinaryFormatter Basics


The second project in the attached package is similar to the first one, except some minor changes:

    1.  The use of XmlSerializer is substituted with BinaryFormatter;
    2.  The attribute “[Serializable]” is added ahead of each class;
    3.  A build-in graphic type “Brush” is added to the DerivedClass;

The same tests are performed on the classes described above. The advantages of BinaryFormatter are:

  • All the public and private fields in an object are capable to be serialized (Test Case 11);
  • No need to declare the default constructor any more. (Test Case 12) But it’s always a good practice to generate a default constructor along with the parameterized one;
  • Almost all build-in types are supported with a few exceptions such as graphic objects, with which the Serializable attribute is not defined. (Test Case 14)
  • Static field is not serializable, because it’s non-object referenced (it’s not part of the object), as shown in the follow picture (Test Case 15).
demo2.PNG
  • 29 KB
  • Static fields not serialized
Static fields not serialized

However, if you do want static members to be serializable, you can implement ISerializable interface to manually add the information and retrieve it back (Test Case 16):
[Serializable]
    public class BuildinType: ISerializable
    {
        static int instanceCount = 0;

        public BuildinType(SerializationInfo info, StreamingContext context)
        {
            BuildinType.instanceCount = info.GetInt32("instanceCount");
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("instanceCount", instanceCount, typeof(int));
        }

                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:

Select allOpen in new window


Now the value of instanceCount is persistent.
demo5.PNG
  • 21 KB
  • Implement ISerializable to serialize static member
Implement ISerializable to serialize static member


  • Binary operation is much faster than string operation (Test Case 16):
demo3.PNG
  • 14 KB
  • Execution time and output file size
Execution time and output file size

  • The Dictionary type is also supported, with a little more cost (Test Case 17).
demo4.PNG
  • 16 KB
  • A little slower to serialize Dictionary type
A little slower to serialize Dictionary type



Basically, you don’t need to worry too much about your data types, just put SerializableAttribute on your class. Then you can achieve persistency by saving the object wherever it needs to. For the types that can not be persisted properly, you can either put NonSerializedAttribute on the data member for the serializer to ignore it, or implement ISerializable interface to make it serializable.


Example of use


From the above experiments, we can see that it’s natural to favor BinaryFormatter over XmlSerializer. Even for configuration settings, it is recommend to modify the data through user interface, rather than directly touching the data in the output files. The third project in the attached package provides two more helper function to save and load data without encryption.
public static void TSerialize(object theObject, string sFileName)
        {
            BinaryFormatter btFormatter = new BinaryFormatter();
            FileStream theFileStream = new FileStream(sFileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
            btFormatter.Serialize(theFileStream, theObject);
            theFileStream.Close();
        }

        public static object TDeSerialize(Type theType, string sFileName)
        {
            if (sFileName == null || sFileName == "" || !File.Exists(sFileName))
            {
                return null;
            }
            FileStream theFileStream = new FileStream(sFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            BinaryFormatter btFormatter = new BinaryFormatter();
            object theObj = btFormatter.Deserialize(theFileStream);
            theFileStream.Close();
            return theObj;
        }

                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:

Select allOpen in new window


As well as the functions using a simple encryption and decryption method:
public static void SerializeWithEncrypt(object theObject, string sFileName)
        {
            MemoryStream theMS = new MemoryStream();
            BinaryFormatter btFormatter = new BinaryFormatter();
            btFormatter.Serialize(theMS, theObject);
            theMS.Seek(0, SeekOrigin.Begin);
            byte[] temp = theMS.ToArray();

            temp = Encrypt(temp);
            //Output to a file.
            FileStream theFileStream = new FileStream(sFileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
            BinaryWriter theBW = new BinaryWriter(theFileStream);

            theBW.Write(temp, 0, temp.Length);
            theBW.Close();
            theFileStream.Close();
            theMS.Dispose();
        }

        public static object DeSerializeWithDecrypt(string sFileName)
        {
            if (sFileName == null || sFileName == "" || !File.Exists(sFileName))
            {
                return null;
            }

            byte[] temp = File.ReadAllBytes(sFileName);

            temp = Decrypt(temp);

            MemoryStream theMS = new MemoryStream(temp);
            BinaryFormatter btFormatter = new BinaryFormatter();
            object theObj = btFormatter.Deserialize(theMS);
            theMS.Dispose();
            return theObj;
        }

                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:

Select allOpen in new window


The Configuration class is implemented as singleton. The persistent data is loaded upon the first time call to create the single instance:
[Serializable]
    public sealed class Configuration
    {
        private static Configuration instance = null;

        private Configuration()
        {
        }

        public static Configuration Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = (Configuration)Utility.TDeSerialize("test.dat");
                }
                if (instance == null)
                {
                    instance = new Configuration();
                }
                return instance;
            }
        }
…
…

                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:

Select allOpen in new window


All the above code can be found in the attached package.

Another attached application, TCPaint, uses exactly the same code to persist the size and location of the form as well as other configuration setting data such as MRU (most recent used files). The unlimited steps of undo and redo actions are also saved using this technique. A user can always rewind and modify their drawings as a set of individual objects rather than as a bitmap image.

In the end, using serialization properly can save you a lot time and headaches.
TSerializerSample.zip
  • 19 KB
  • The package contains 3 projects
TSerializerSample.zip

TCPaint.zip
  • 215 KB
  • A real life example
TCPaint.zip
    Asked On
    2010-12-21 at 14:44:16ID4275
    Tags

    Persistence Serialization XmlSerializer BinaryFormatter

    Topic

    C# Programming Language

    Views
    1873

    Comments

    Add your Comment

    Please Sign up or Log in to comment on this article.

    Join Experts Exchange Today

    Gain Access to all our Tech Resources

    Get personalized answers

    Ask unlimited questions

    Access Proven Solutions

    Search 3.2 million solutions

    Read In-Depth How-To Guides

    1000+ articles, demos, & tips

    Watch Step by Step Tutorials

    Learn direct from top tech pros

    And Much More!

    Your complete tech resource

    See Plans and Pricing

    30-day free trial. Register in 60 seconds.

    Loading Advertisement...

    Top C# Experts

    1. kaufmed

      566,376

      Sage

      500 points yesterday

      Profile
      Rank: Genius
    2. BuggyCoder

      240,764

      Guru

      10 points yesterday

      Profile
      Rank: Sage
    3. navneethegde

      158,560

      Guru

      0 points yesterday

      Profile
      Rank: Wizard
    4. CodeCruiser

      140,708

      Master

      2,000 points yesterday

      Profile
      Rank: Genius
    5. TheLearnedOne

      137,350

      Master

      2,800 points yesterday

      Profile
      Rank: Savant
    6. wdosanjos

      124,511

      Master

      3,500 points yesterday

      Profile
      Rank: Genius
    7. AndyAinscow

      107,357

      Master

      0 points yesterday

      Profile
      Rank: Genius
    8. Dhaest

      98,335

      Master

      0 points yesterday

      Profile
      Rank: Genius
    9. Idle_Mind

      91,914

      Master

      0 points yesterday

      Profile
      Rank: Savant
    10. tommyBoy

      90,068

      Master

      0 points yesterday

      Profile
      Rank: Genius
    11. nishantcomp2512

      89,000

      Master

      0 points yesterday

      Profile
      Rank: Wizard
    12. MlandaT

      86,553

      Master

      0 points yesterday

      Profile
      Rank: Genius
    13. Chinmay_Patel

      80,818

      Master

      0 points yesterday

      Profile
      Rank: Genius
    14. ddayx10

      66,538

      Master

      0 points yesterday

      Profile
      Rank: Sage
    15. anarki_jimbel

      66,132

      Master

      2,000 points yesterday

      Profile
      Rank: Genius
    16. ambience

      63,764

      Master

      0 points yesterday

      Profile
      Rank: Sage
    17. ukerandi

      62,593

      Master

      1,000 points yesterday

      Profile
      Rank: Guru
    18. apeter

      60,772

      Master

      0 points yesterday

      Profile
      Rank: Sage
    19. JamesBurger

      60,305

      Master

      0 points yesterday

      Profile
      Rank: Sage
    20. sedgwick

      52,750

      Master

      1,600 points yesterday

      Profile
      Rank: Genius
    21. emoreau

      50,917

      Master

      0 points yesterday

      Profile
      Rank: Genius
    22. ged325

      50,311

      Master

      2,000 points yesterday

      Profile
      Rank: Genius
    23. anuradhay

      49,977

      2,500 points yesterday

      Profile
      Rank: Guru
    24. techChallenger1

      47,638

      0 points yesterday

      Profile
      Rank: Guru
    25. mas_oz2003

      43,540

      0 points yesterday

      Profile
      Rank: Genius

    Hall Of Fame