Daniel G
Daniel G

Reputation: 245

C#: Writing and Reading List to and from Binary File

I wrote a program that has a User class, which is saved to file to store user information. However, there can be multiple different users, so instead of saving the file as a single User, it's a List of Users. This is in XNA, and therefore a game, but this shouldn't really have any affect on things. Users have Coins, Skins, their Inventory, their Username, their Password(if they have one), and a hasPassword bool. Because users have coins, I don't want this to be an editable text file, so I saved it as a binary file. I wrote some code, tested it, and everything was fine and dandy, until I tried to save multiple users to the List. For some reason, whenever I saved a second user and read in the List, the List only had one object (the first User). Here, I'll provide the code and an example to make this easier to understand:

[Serializable]
class User
{
    #region Fields & Properties

    public string Username = "";
    public string Password = "";
    public bool HasPassword = false;

    public int Coins = 0;
    public List<Achievement> AchievementsCompleted = new List<Achievement>();
    public List<InventoryItem> Inventory = new List<InventoryItem>();
    public List<string> Skins = new List<string>();

    public string CurrentSkinAsset { get; set; }

    const int SPACING = 10;

    const string FILE_PATH = "Users.bin";

    #endregion

    #region Constructors

    public User(string username, int coins, string skinPath, ContentManager content)
    {
        Username = username;
        CurrentSkinAsset = skinPath;
    }
    public User(string username, string password, int coins, string skinPath, ContentManager content) 
        : this(username, coins, skinPath, content)
    {
        HasPassword = true;
        Password = password;
    }

    #endregion

    #region Public Methods

    public static List<User> LoadUsers()
    {
        FileStream fileStream = null;
        List<User> returnList = null;
        try
        {
            if (File.Exists(FILE_PATH))
            {
                fileStream = new FileStream(FILE_PATH, FileMode.Open, FileAccess.Read);
                BinaryFormatter deserializer = new BinaryFormatter();
                returnList = (List<User>)deserializer.Deserialize(fileStream);
            }
            else
            {
                fileStream = File.Create(FILE_PATH);
                List<User> users = new List<User>();
                BinaryFormatter serializer = new BinaryFormatter();
                serializer.Serialize(fileStream, users);
            }
        }
        catch (Exception e)
        {
            Console.WriteLine("There's been an error. Here is the message: " + e.Message);
        }
        finally
        {
            if (fileStream != null)
            {
                fileStream.Close();
            }
        }

        return returnList;
    }

    public void SerializeUser()
    {
        // Also works
        FileStream fileStream = null;
        List<User> users;
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        try
        {
            if (File.Exists(FILE_PATH))
            {
                fileStream = File.Open(FILE_PATH, FileMode.Open);
                //fileStream = new FileStream(FILE_PATH, FileMode.Open, FileAccess.ReadWrite);
                users = (List<User>)binaryFormatter.Deserialize(fileStream);
            }
            else
            {
                fileStream = File.Create(FILE_PATH);
                users = new List<User>();
            }

            for (int i = 0; i < users.Count; i++)
            {
                if (users[i].Username.ToLower() == this.Username.ToLower())
                {
                    users.Remove(users[i]);
                }
            }
            users.Add(this);
            binaryFormatter.Serialize(fileStream, users);
        }
        catch (Exception e)
        {
            Console.WriteLine("There's been an error. Here is the message: " + e.Message);
        }
        finally
        {
            if (fileStream != null)
            {
                fileStream.Close();
            }
        }
    }

    #endregion
}

Here is the test code I wrote (in the Game1 LoadContent method):

User test = new User("Bob", 0, "skin1", Content)
test.SerializeUser();

I looked in the Debug folder and found a new file called "Users.bin". It had a bunch of random characters and such, as is expected. I changed test's username parameter from "Bob" to "Joe", which should add a new User. But...

Here's the code to read the users:

List<User> testList = User.LoadUsers();

I inserted a breakpoint at the next line of code, and hovered over testList, but the only visible user was Bob, and Joe didn't exist. Any ideas? Also: I'm thirteen. I may not understand all the terms you use, as I haven't been programming for a super long time. Thanks in advance!

Upvotes: 3

Views: 1599

Answers (1)

Chris Shain
Chris Shain

Reputation: 51369

The problem is that when you save your list of users, you are opening the FileStream, reading in the list (to replace the current user), and then saving the new list to the FileStream without resetting the position. The initial read advances the FileStream to the end of the file- the save then appends the new list to the FileStream after the initial list.

What happens then is you have a file that looks like this:

*start of file*
List(User1)
List(User1,User2)
*end of file*

and every time you read it, you are only getting that initial list.

Try this:

        users.Add(this);
        filStream.Position = 0;
        binaryFormatter.Serialize(fileStream, users);

For what it's worth, this is a very inefficient way to deal with saving and loading your users. You are essentially reading and writing the whole list for every change to any user. It's also not thread safe (meaning if this is a multiplayer game with multiple simultaneous updates, you might get corruption in your users file depending on how you open it and what updates happen). You might want to look into a proper database rather than a plain file.

Upvotes: 3

Related Questions