Introduction
The majority of projects I get stuck into, have a requirement to save/load some kind of object - be that a custom object that stores specific settings for the application (no, I DON'T want to slap them into app.config, thank you very much - that can get messy very fast), or a lightweight list of something I am using in my application. Invariably, it boils down to saving the blessed thing somewhere and then load it back - it should be simple, so I don't want to have to re-write the code every time or call on some heavy library to manage things for me. This short article describes some very simple code that has become a regular part of my "code toolbelt" - I use it pretty much every other day for something or other that involves serialization.
It's good to share, so enjoy. :)
NB
While the code described here is in C#, I have also provided a VB version in the download link.
Background - The Way Things Were
To get started, let's start off with a very basic example class that we want to serialize. You know it - basic stuff, it has a constructor and a const that indicates where I want it to save.
- class SimpleClass {
- const string FileSavePath = @ "c:\data\SimpleClass.xml";
- public Guid ID {
- get;
- set;
- }
- public string CityName {
- get;
- set;
- }
- public int Rank {
- get;
- set;
- }
- public bool Active {
- get;
- set;
- }
- public List < string > RandomList {
- get;
- set;
- }
- public SimpleClass() {
- ID = Guid.NewGuid();
- RandomList = new List < string > ();
- }
- }
In the past, when I wanted to save and load such an object, I would build customized save and load methods that called on the XMLSerializer class to convert my object to an XML representation, and a stream reader/writer to get the XML to/from disk. I'm sure you've done the same thing yourself on occasion.
Basic object save method
- public void BasicSave() {
- var xs = new XmlSerializer(typeof(SimpleClass));
- using(TextWriter sw = new StreamWriter(FileSavePath)) {
- xs.Serialize(sw, this);
- }
- }
Basic object load method
- public void BasicLoad() {
- var xs = new XmlSerializer(typeof(SimpleClass));
- using(var sr = new StreamReader(FileSavePath)) {
- var tempObject = (SimpleClass) xs.Deserialize(sr);
- ID = tempObject.ID;
- CityName = tempObject.CityName;
- Rank = tempObject.Rank;
- Active = tempObject.Active;
- RandomList = tempObject.RandomList;
- }
- }
These methods are fine. They work but I have to re-write them and customize them each time I reuse ... not great - we can do better!
Getting a Bit Abstract
The next step is to create a separate class to manage the save/load. With this, things are already better from a re-use point of view. Here, we have a class GenericUtils, and the save method.
The class is static, so I don't have to implicitly create it. The Save method takes an Object type "T", and two method params: 'FileName' - where we want the serialized object to save, and 'Object', the object itself that we are going to serialize. The method is a simple expansion of the original save method. The main difference is that instead of explicitly casting using the class 'SImpleObject', we rather extract the typeof the variable T and use that as the XmLSerializer input param.
- public static class GenericUtils {
- public static bool Save < T > (string FileName, Object Obj) {
- var xs = new XmlSerializer(typeof(T));
- using(TextWriter sw = new StreamWriter(FileName)) {
- xs.Serialize(sw, Obj);
- }
- if (File.Exists(FileName)) return true;
- else return false;
- }
- }
Now, when we want to save, we call the generic method, passing in the object type
<SimpleClass>, the
fileName where we want to save, and a reference to the object being saved
this.
- public bool SaveGeneric1(string fileName) {
- return GenericUtils.Save < SimpleClass > (fileName, this);
- }
So far so good. Let's look at the Load method.
Load returns a generic Object, and is called by sending in an object type and the path/name of the file where the object we want to access has been previously saved. It's the same code as before, but made general and abstracted.
The Load method checks to see if the file is being asked for exists, and assuming it does, it attempts to deserialize it. The main trick here is that we typecast the object type for the deserializer by using the generic T value that is sent in when we call the method.
- public static class GenericUtils {
- public static T Load < T > (string FileName) {
- Object rslt;
- if (File.Exists(FileName)) {
- var xs = new XmlSerializer(typeof(T));
- using(var sr = new StreamReader(FileName)) {
- rslt = (T) xs.Deserialize(sr);
- }
- return (T) rslt;
- } else {
- return default (T);
- }
- }
- }
As for save, now when we want to load, we call the generic method, passing in the object type
<SimpleClass>, the
fileName where the serialized object that we want to load is located, and a reference to the object being saved
this.
- public void LoadGeneric1(string fileName) {
- var fileData = GenericUtils.Load < SimpleClass > (fileName);
- ID = fileData.ID;
- CityName = fileData.CityName;
- Rank = fileData.Rank;
- Active = fileData.Active;
- }
OK, so that's a little bit cleaner, but we can do better!
When we save the object, we are always sending in the type to the save method. There's no need to do this. We can read the type on the fly just before we save. This cuts down some more code.
- public static bool Save2(string FileName, Object Obj) {
- var xs = new XmlSerializer(Obj.GetType());
- using(TextWriter sw = new StreamWriter(FileName)) {
- xs.Serialize(sw, Obj);
- }
- if (File.Exists(FileName)) return true;
- else return false;
- }
The change here, is that when we declare the
XmlSerializer, instead of passing in the type received by the method being called, we get the type on the fly
Obj.GetType().
This means that the calling code for the method is far cleaner.
- public bool SaveGeneric2(string fileName) {
- return GenericUtils.Save2(fileName, this);
- }
So, that's the whirlwind tour. There is more related code I use that serializes to JSON - I'll add that in when I get some time!
Until then, happy coding.