Click here to Skip to main content
15,878,959 members
Articles / Programming Languages / Visual Basic

Generic C# .NET Object Save/Load

Rate me:
Please Sign up or sign in to vote.
4.36/5 (18 votes)
12 Jun 2016CPOL4 min read 40.6K   724   23   11
Useful code for your toolbelt - generic save/load an object in XML

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 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.

C#
 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.

(1) Basic object save method:

C#
public void BasicSave()        {
    var xs = new XmlSerializer(typeof(SimpleClass));

    using (TextWriter sw = new StreamWriter(FileSavePath))
    {
        xs.Serialize(sw, this);
    }
}

(2) Basic object load method:

C#
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 instead extract the typeof the variable T and use that as the XmLSerializer input param.

C#
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.

C#
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 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.

C#
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.

C#
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:

C#
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:

C#
public bool SaveGeneric2(string fileName)
{
    return GenericUtils.Save2(fileName, this);
}

Next...

Ok - I know it seems sudden to stop here, but I had to get this much published now as it is describing a concept to a colleague who is converting over to C#, and shares the learning at the same time! .... As soon as I get some time I will update this article to show how to save/load with JSON, and also we will go a bit deeper and use reflection to implement a deep copy of our object.

History

  • Version 1(ish!) - June 12, 2016

License

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


Written By
Chief Technology Officer SocialVoice.AI
Ireland Ireland
Allen is CTO of SocialVoice (https://www.socialvoice.ai), where his company analyses video data at scale and gives Global Brands Knowledge, Insights and Actions never seen before! Allen is a chartered engineer, a Fellow of the British Computing Society, a Microsoft mvp and Regional Director, and C-Sharp Corner Community Adviser and MVP. His core technology interests are BigData, IoT and Machine Learning.

When not chained to his desk he can be found fixing broken things, playing music very badly or trying to shape things out of wood. He currently completing a PhD in AI and is also a ball throwing slave for his dogs.

Comments and Discussions

 
QuestionThe example file doesn't work. Pin
Tomas19-Sep-19 20:01
Tomas19-Sep-19 20:01 
QuestionHmmm. The intro is a little bit misleading. Pin
Pete O'Hanlon13-Jun-16 0:01
mvePete O'Hanlon13-Jun-16 0:01 
AnswerRe: Hmmm. The intro is a little bit misleading. Pin
DataBytzAI13-Jun-16 0:06
professionalDataBytzAI13-Jun-16 0:06 
AnswerRe: Hmmm. The intro is a little bit misleading. Pin
John Brett13-Jun-16 5:38
John Brett13-Jun-16 5:38 
QuestionAwesome but what about... Pin
Alaa Ben Fatma12-Jun-16 22:24
professionalAlaa Ben Fatma12-Jun-16 22:24 
AnswerRe: Awesome but what about... Pin
DataBytzAI12-Jun-16 23:02
professionalDataBytzAI12-Jun-16 23:02 
GeneralRe: Awesome but what about... Pin
Alaa Ben Fatma12-Jun-16 23:23
professionalAlaa Ben Fatma12-Jun-16 23:23 
QuestionDownload Link? Pin
George Swan12-Jun-16 20:49
mveGeorge Swan12-Jun-16 20:49 
AnswerRe: Download Link? Pin
DataBytzAI12-Jun-16 21:20
professionalDataBytzAI12-Jun-16 21:20 
Questionif you use WCF DataContract and DataMember this can be much simpler, and handle a wider range of "Types" Pin
BillWoodruff12-Jun-16 20:35
professionalBillWoodruff12-Jun-16 20:35 
AnswerRe: if you use WCF DataContract and DataMember this can be much simpler, and handle a wider range of "Types" Pin
DataBytzAI12-Jun-16 21:22
professionalDataBytzAI12-Jun-16 21:22 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.