Click here to Skip to main content
14,546,625 members

Generic C# .NET Object Save/Load

Rate this:
4.36 (18 votes)
Please Sign up or sign in to vote.
4.36 (18 votes)
12 Jun 2016CPOL
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.

 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:

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

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

(2) 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 instead 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 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);
}

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)

Share

About the Author

DataBytzAI
Chief Technology Officer The DataWorks
United Kingdom United Kingdom
Allen is a consulting architect with a background in enterprise systems. His current obsessions are IoT, Big Data 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 runs his own company specializing in systems architecture and scaling for big data and is involved in a number of technology startups.

Allen is a chartered engineer, a Fellow of the British Computing Society, and a Microsoft MVP. He writes for CodeProject, C-Sharp Corner and DZone. 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
MemberTomas19-Sep-19 20:01 
QuestionHmmm. The intro is a little bit misleading. Pin
Pete O'Hanlon13-Jun-16 0:01
subeditorPete 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
MemberJohn 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
MemberGeorge 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
mveBillWoodruff12-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.

Article
Posted 12 Jun 2016

Stats

25.4K views
558 downloads
22 bookmarked