How to save and load objects to and from file in C#

Persistency is almost always a requirement for applications that are meant for serious usage. Perhaps we want the data that our C# program had harvested or generated to be available everytime it runs. Or for that load of data that we are unable to send to a server to be remembered, so that we can try sending at a later time.

Because most of the data that is held by a C# application at runtime is in the form of objects, it is convenient to be able to save and load objects to file directly. Such capability is dubbed object serialization, and like many other programming languages, C# has the facilities to perform object serialization for developers.

Remembering our friends

Suppose we want to write a C# program that can remember our friends' email addresses. Instead of using System.Data.SQLite, let's use object serialization to achieve the data persistence aspect of our program.

The serializable Friend class

In order to be able to save and load objects, the class has to be indicated as being serializable. We mark a class with the Serializable attribute to indicate that we want to be able to save its objects:

using System;
using System.Text;

[Serializable]
public class Friend
{
    private string name;
    private string email;

    public Friend (string name, string email) {
        this.name = name;
        this.email = email;
    }

    public string Name
    {
        get
        {
            return this.name;
        }

        set
        {
            this.name = value;
        }
    } // end public string Name

    public string Email
    {
        get
        {
            return this.email;
        }
        set
        {
            this.email = value;
        }
    } // end public string Email
} // end public class Friend

Here, the Friend class is a data wrapper class that we will use to hold the name and email address of a particular friend. As we add more friends to our program, more Friend instances will be created. Such instances will be maintained by the FriendsInformation class.

The FriendsInformation class

Let's create a singleton class, FriendInformation, that will be responsible for the saving and loading of information of our friends.

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;

public class FriendsInformation
{

    private static FriendsInformation friendsInformation;

    private Dictionary<string, Friend> friendsDictionary;
    private BinaryFormatter formatter;

    private const string DATA_FILENAME = "friendsinformation.dat";

    public static FriendsInformation Instance()
    {
        if (friendsInformation == null) {
            friendsInformation = new FriendsInformation();
        } // end if

        return friendsInformation;
    } // end public static FriendsInformation Instance()

    private FriendsInformation()
    {
        // Create a Dictionary to store friends at runtime
        this.friendsDictionary = new Dictionary<string, Friend>();
        this.formatter = new BinaryFormatter();
    } // end private FriendsInformation()

    public void AddFriend(string name, string email)
    {
        // If we already had added a friend with this name
        if (this.friendsDictionary.ContainsKey(name))
        {
            Console.WriteLine("You had already added " + name + " before.");
        }
        // Else if we do not have this friend details 
        // in our dictionary
        else
        {
            // Add him in the dictionary
            this.friendsDictionary.Add(name, new Friend(name, email));
            Console.WriteLine("Friend added successfully.");
        } // end if
    } // end public bool AddFriend(string name, string email)

    public void RemoveFriend(string name)
    {
        // If we do not have a friend with this name
        if (!this.friendsDictionary.ContainsKey(name))
        {
            Console.WriteLine(name + " had not been added before.");
        }
        // Else if we have a friend with this name
        else
        {
            if (this.friendsDictionary.Remove(name))
            {
                Console.WriteLine(name + " had been removed successfully.");
            }
            else
            {
                Console.WriteLine("Unable to remove " + name);
            } // end if
        } // end if
    } // end public bool RemoveFriend(string name)

    public void Save()
    {
        // Gain code access to the file that we are going
        // to write to
        try
        {
            // Create a FileStream that will write data to file.
            FileStream writerFileStream = 
                new FileStream(DATA_FILENAME, FileMode.Create, FileAccess.Write);
            // Save our dictionary of friends to file
            this.formatter.Serialize(writerFileStream, this.friendsDictionary);

            // Close the writerFileStream when we are done.
            writerFileStream.Close();
        }
        catch (Exception) {
            Console.WriteLine("Unable to save our friends' information");
        } // end try-catch
    } // end public bool Load()
             
    public void Load() 
    {
     
        // Check if we had previously Save information of our friends
        // previously
        if (File.Exists(DATA_FILENAME))
        {

            try
            {
                // Create a FileStream will gain read access to the 
                // data file.
                FileStream readerFileStream = new FileStream(DATA_FILENAME, 
                    FileMode.Open, FileAccess.Read);
                // Reconstruct information of our friends from file.
                this.friendsDictionary = (Dictionary<String, Friend>)
                    this.formatter.Deserialize(readerFileStream);
                // Close the readerFileStream when we are done
                readerFileStream.Close();

            } 
            catch (Exception)
            {
                Console.WriteLine("There seems to be a file that contains " +
                    "friends information but somehow there is a problem " +
                    "with reading it.");
            } // end try-catch

        } // end if
        
    } // end public bool Load()

    public void Print()
    {
        // If we have saved information about friends
        if (this.friendsDictionary.Count > 0)
        {
            Console.WriteLine("Name, Email");
            foreach (Friend friend in this.friendsDictionary.Values)
            {
                Console.WriteLine(friend.Name + ", " + friend.Email);
            } // end foreach
        }
        else
        {
            Console.WriteLine("There are no saved information about your friends");
        } // end if
    } // end public void Print()

} // end public class FriendsInformation

Encapsulating some facilitators

To facilitate the representation, addition and removal of friends information, we maintain an instance of the System.Collections.Generic.Dictionary class as an instance variable.

On the other hand, to be able to serialize (save) and deserialize (load), we maintain an instance of the System.Runtime.Serialization.Formatters.Binary.BinaryFormatter class.

Adding new friend

The AddFriend method allows callers to add the name and email of a friend to the underlying Dictionary instance, via its Add method. If we had already added a friend with the same name, the friend will not be added.

Removing a friend

The RemoveFriend method allows callers to remove a friend, based on his name, from our underlying Dictionary instance, via its Remove method. If there is no friend with the supplied name, no removal will take place.

Saving Friend objects to file

The Save method is all about writing to file. We first obtain an instance of System.IO.FileStream which has write access to friendsinformation.dat.

If we are successful in doing so, we use the Serialize method of our BinaryFormatter instance to save the entire friendsDictionary to friendsinformation.dat.

Loading Friend objects from file

On the contrary, the Load method is all about reading from file. We first check whether there are previous calls to the Save method by checking the existence of friendsinformation.dat.

If friendsinformation.dat exists, we first obtain an instance of FileStream that has read access to friendsInformation.dat. We then use the Deserialize to read from the FileStream instance.

When the Deserialize method is called successfully, a Dictionary instance, which contains Friend objects that were previously saved to file, will be created. This Dictionary instance is then set to the friendsDictionary variable.

Printing of friends' information

The Print method allows us to inspect the Friend objects that are maintained by our program at runtime. It iterates the list of Friend objects and outputs the name and email.

A simple command line application

To sum up this post, let's create a command line application that we can use to remember the names and email addresses of our friends.


using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

public class Program
{
    private static string programName = 
        Path.GetFileNameWithoutExtension(System.AppDomain.CurrentDomain.FriendlyName);

    public static void Main(string[] args)
    {

        if (args.Length < 1)
        {
            PrintUsage();
        }
        else
        {

            FriendsInformation fi = FriendsInformation.Instance();
            fi.Load();

            String command = args[0].ToLower();

            if (command == "list") 
            {
                fi.Print();
            }
            else if (command == "add") {

                if (args.Length >= 3)
                {
                    String name = args[1];
                    String email = args[2];
                    fi.AddFriend(name, email);
                    fi.Save();
                }
                else
                {
                    PrintUsage();
                    return;
                } // end if
                
            }
            else if (command == "remove")
            {

                if (args.Length >= 2)
                {
                    String name = args[1];
                    fi.RemoveFriend(name);
                    fi.Save();
                }
                else
                {
                    PrintUsage();
                    return;
                } // end if

            }
            else
            {
                Console.WriteLine("Command not found: " + command);
                PrintUsage();
            } // end if

                
        } // end if

    } // end public static void Main(string[] args)

    public static void PrintUsage() 
    {
        Console.WriteLine("Usage: ");
        Console.WriteLine(programName + " list");
        Console.WriteLine(programName + " add <name> <email>");
        Console.WriteLine(programName + " remove <name>");
    } // end public static void Main(string[] args)
} // end public class Program

The application accepts three commands via the command line:

  1. list displays the names and email addresses of all our friends which we have added to our program.
  2. add allows us to add a new friend to our program.
  3. remove allows us to remove a friend from our program.

For each of the 3 commands, the FriendsInformation singleton is created and called upon to attempt to load previously saved Friend objects into runtime.

Except for the list command, the Save method of the FriendsInformation is called to save any changes made to the collection of Friend objects back into friendsInformation.dat.

After several execution of the add and remove commands, friendsInformation.dat should appear in the same folder as our command line application. To check whether our command line application can remember (or forget) our friends, we can execute the list command.

Related posts

About Clivant

Clivant a.k.a Chai Heng enjoys composing software and building systems to serve people. He owns techcoil.com and hopes that whatever he had written and built so far had benefited people. All views expressed belongs to him and are not representative of the company that he works/worked for.