DevCity.NET -
OOP: Create, Collect, Sort, Save and Retrieve Objects
Ged Mead

Ged Mead (XTab) is a Microsoft Visual Basic MVP who has been working on computer software and design for more than 25 years. His journey has taken him through many different facets of IT. These include training as a Systems Analyst, working in a mainframe software development environment, creating financial management systems and a short time spent on military laptop systems in the days when it took two strong men to carry a 'mobile' system.

Based in an idyllic lochside location in the West of Scotland, he is currently involved in an ever-widening range of VB.NET, WPF and Silverlight development projects. Now working in a consultancy environment, his passion however still remains helping students and professional developers to take advantage of the ever increasing range of sophisticated tools available to them.

Ged is a regular contributor to forums on vbCity and authors articles for DevCity. He is a moderator on VBCity and the MSDN Tech Forums and spends a lot of time answering technical questions there and in several other VB forum sites. Senior Editor for DevCity.NET, vbCity Developer Community Leader and Admin, and DevCity.NET Newsletter Editor. He has written and continues to tutor a number of free online courses for VB.NET developers.

by Ged Mead
Published on 3/18/2007

  Several recent posts in the VBCity Forums made me realise that it might be useful to have an article that dealt with some of the basic requirements when it comes to creating your own classes and objects.  

   Many beginners start by creating a simple game and this is often a good learning project so we will use this approach in this article.  Two key elements of a game application like this will probably be a way of creating Players and keeping a record of their High Scores . Users can log in to the game using their Player name and see their last score.

   As they play again in this session they might increase this score or start over from zero. They might even want to see how their score rates against other Players.    So we would need a way of creating a collection of Players and their Scores.  And as we usually want to see the Scores listed from highest to lowest, there will have to be a way to sort those Score values that are stored in the collection.

   Finally, it wouldn't be much use if the Scores were all lost each time the application ends, so we should also build in a way of saving the latest data to the hard drive and of course a means of retrieving  them when you need them.

   This article for beginners uses VB 2005 and shows you how to meet all these requirements.

The Player Class

   The first thing we are going to do is to build a very simple Player class. Using this class we will be able to create individual Player objects (or Instances of the Player Type ).

Getting Started

   I have used the Visual Basic 2005 Express Edition of Visual Studio 2005 for all the screenshots and code demonstrations. If you use a different version, some of the screens will be slightly different, but not enough to cause you any major problems.

   Create a new project if you want to build the code as we work through the article. Select Windows Application as the project type. I have named my version "PlayerDemo".

  By default, there will be a Form named Form1 in the Solution Explorer pane. We will use this later, but first I want to create the basic Player Class. So, to do this, in the IDE main menu select Project > Add Class:

To rename this new class, type in "Player.vb" and press Enter.

Initial Class Code

  This article is aimed at relative newbies, but that's a description that spreads across a lot of different learning levels.   If you have created classes already, the following description will be easy for you to follow.  However, if you are a total beginner and are already wondering what it's all about then you may be interested in learning the basics of classes by participating in the VBCity Academy OOP1 course. This online course is cost free to VBCity Members and is available from the VBCity Academy. Further details, including upcoming course dates, are announced in the Academy Forum.

   As I described on the previous page, this class is designed to keep track of players in a game, together with their scores. So we need to have properties for both Name and Score.   In order to create new players we have to include a constructor (Sub New).   You'll be very familiar with the "New" keyword and will have used it many times,  e.g. 

Dim alMYPictures As New ArrayList

Dim MyPic As New Bitmap("Mugshot.bmp")

   and so on.   But if you haven't created your own classes before, you may not know that this is the code that allows us to create new objects by using the New keyword .

  So in this article we will create a constructor for the class.   In fact, for convenience, we will also create a second version of the Player constructor (usually referred to as an overloaded version).  This isn't strictly necessary, but it will allow us to create Player objects and assign a Name and a Score at the same time - similar to the Bitmap example above, which allows us to pass in the name of the new bitmap when it is created.  The text in the brackets above is known as a parameter.

  Also for convenience, the class will have a ToString method. You will probably be familiar with this method because the .NET Framework Types all have a ToString method that you can select from the Intellisense list.   What it actually does is that it returns a string representation of the object - or in plain English -  some text that will be meaningful to users. 

   Finally, although this is a very simple class, we should still follow recommended OOP principles and use Private fields to hold the data that is assigned to the Name and Score properties.   In very general terms,  these Private fields shield the important data by storing it safely inside the class itself.  Code that accesses the class cannot access or change the values inside those fields directly; this must be done via the Name and Score Properties described above.

So, in summary, the Player Class will include :

  • two Properties - Name and Score,
  • two backing Fields for those properties - m_Name and m_Score,
  • a Default Constructor and an Overloaded Constructor
  • a ToString method


   Although we will make some changes later, here is the first pass at our code for the Player Class:

Public Class Player

' Two Fields
Private m_Name As String
Private m_Score As Integer

' Two Properties
Public Property Score() As Integer
   Return m_Score
  End Get
  Set(ByVal Value As Integer)
   m_Score = Value
  End Set
End Property

Public Property Name() As String
  Return m_Name
  End Get
  Set(ByVal Value As String)
   m_Name = Value
  End Set
End Property

' Two Constructors
Public Sub New()
End Sub

Public Sub New(ByVal playerName As String, ByVal playerScore As String)
   Name = playerName
   Score = playerScore
End Sub

' ToString method
Public Overrides Function ToString() As String
  Return m_Name
End Function

End Class

Creating Player Objects
   We'll set up some controls on that Form1 so we can create new Player instances and assign values to their properties. The screenshot below shows the layout I've used. A couple of TextBoxes, a Button and a Label to display the result.

With the following code in the Button Click event, we can test the class:

  Dim Player1 As New Player(TextBox1.Text, CInt(TextBox2.Text))

   Label3.Text = String.Format("Player Name: {0} {1}Current Score: {2}", Player1.Name, ControlChars.CrLf, Player1.Score.ToString)

  The first line creates a new Player object and assigns what it finds in TextBox1 as the Name and what it finds in TextBox2 as the Score.  

   The remaining lines (actually it is all one line, but may be too long to fit on screen, depending on your monitor resolution):  This takes the values that we have assigned to the Player object and displays them in a particular format so that we can see that the class code works properly.   I know that the String.Format syntax looks a bit weird if you haven't seen it before, but it actually is a big help when you want to create complex strings that pull information in from all over the place.  If you find the above confusing then you can think of it like this:

  Label3.Text = "Player Name: "

  Label3.Text &= Player1.Name

  Label3.Text &= ControlChars.CrLf

  Label3.Text &= "Current Score: "

  Label3.Text &= Player1.Score.ToString

    Once you have created this first part of the project, have a play with it, changing the Name and the Score a couple of times.  

   One thing you will probably quickly find out is that it doesn't take much to crash the project as it stands.  That's mainly because we are trusting the user to enter valid Names and Scores in those TextBoxes; an assumption that is never safe to make, even when the user is you!

  Later we will come back and make some changes to the class, but it is sufficient for our needs for now and we can look at how to create a collection of Players as I mentioned in the introduction.


A Collection of Players

A New Approach

  Prior to the release of VB 2005 creating a collection of objects, such as the Player object, could be quite a fiddly task.  It wasn't rocket science, but did require a little work.  Although you can still use the VB.NET 2002/2003 approach, for the kind of scenario we are working with in this article it's much easier to harness the power of the generic collection to do most of the work for us.

   The great strength of the generic collection is that it automatically gives us a strongly typed collection.   Strongly Typed simply means that it isn't possible to pass an instance of a "wrong" or unacceptable type into a generic collection.   This is very useful because by strongly typing the collection you are effectively locking out any unwanted types that a user might try and add to it.   This in turn means that your application won't crash when - for example - a user took it into his head to do something that tried to add a string to a collection that expected to be asked to store only numbers.   (That's a rather simplistic example, but you get the idea).

    For the purposes of our article, we can create a collection that will only accept Player objects; that is, it is strongly typed to accept those kinds of objects only.

Creating The List

   The code needed is very simple indeed.  We will create a List collection.  This is very similar to the ArrayList, the key difference being its strongly typed nature.   This collection is available from the System.Collections.Generic namespace, so we should first Import this namespace into our form at the very top:

Imports System.Collections.Generic

   I've chosen to create a new form for the code on this page of the article and I named that form Step2.   This is just for demonstration purposes and in your project you can just continue to use the original form.  

   Inside the code for the form (that is, right below the "Public Class Form1"  or "Public Class Step2" line, we can create a new Generic List with:

Dim PlayersList As New List(Of Player)

   i.e. a generic List that will only accept Player instances.

  Here is a revised version of the code that creates a new Player instance.  This version is more robust than our original effort and forces the user to enter at least one letter for the name and a valid numeric value for the score.

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click  

' Create a new Player instance:
Dim Player1 As New Player

 If TextBox1.Text.Length > 0 Then
  Player1.Name = TextBox1.Text
  Player1.Score = CInt(TextBox2.Text)
  Windows.Forms.MessageBox.Show("Please enter a Player Name")
  Exit Sub
 End If
Catch NoScore As InvalidCastException
' Optional message:
Windows.Forms.MessageBox.Show("Please enter a valid score")

Catch ex As Exception

End Try

Label3.Text = String.Format("Player Name: {0} {1}Current Score: {2}", Player1.Name, _ ControlChars.CrLf, Player1.Score.ToString)

   Still in the Button Click event, once we have created a new Player instance we will automatically add it to the collection.   This is just a matter of:- 


Player1 = Nothing        ' This instance is finished with.
End Sub

   Try this amended version of the code in your project and create a few Player instances.

   Of course, at this stage you don't have any confirmation that the collection is being built correctly behind the scenes for you.   So let's deal with that now.   Add another button and a ListBox control to the form:


   What we will do is code the second "Show All Players" button to display the contents of the collection in the ListBox.   Here is the code to do that:

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click

   For Each APlayer As Player In PlayersList
     ListBox1.Items.Add(String.Format("{0}, Current Score: {1}" , APlayer.Name, APlayer.Score.ToString))

End Sub

    Run this code now and add a few new Players, giving them Scores.  Once you have created a few, then click on the new button to see the collection listed for you in the ListBox.   Pretty simple, really.




Sortable Objects - Sorting Players By Name or Score


   Now we have a basic class and a simple collection utility in which we can store Person objects, we can turn our attention to improving the code so that Players can be easily sorted into the correct order.   But what exactly is the "correct" order of Player objects?  The VB.NET compiler certainly can't answer that question for you; the Player Class is your invention and the compiler only knows the details that you tell it.

   So, as a useful exercise for future applications, I am going to say that at times we might want the Players sorted by Name and at other times we may want them sorted by their Score.    So essentially we want to have access to multiple versions of some device that will help us sort the Players.   And of course such a device does indeed exist - and it's the IComparer Interface.

  In its most simple terms an IComparer Interface is a skeleton on which you can add flesh to the bones.   It knows how to sort (something that is quite a complex set of processes, as you'll know if you've ever attempted it yourself), but needs to know what it is being asked to sort.   

   To take a non-technical example, consider a new employee in a factory.    The new worker will understand the general principle of sorting - something is placed in a set position in an order of items.    If the worker was sent to a storehouse where there was a jumbled stack of boxes and told to "sort" them, he would have several options of how the sort should be implemented:  by size, by weight, by the contents name, by the color of the box, by serial number, etc.    In real life he would need to be told by the supervisor what sort order is required and then he would be able to get on with the job.

  And that's quite a reasonable analogy for what IComparer does.  It knows how to sort in general, but needs to be told the specifics.  Let's now look at how you use IComparer.

Sorting by Player.Name

   Amend the Player Class by adding the following lines at the end of the Class code (That is, right above the line that reads "End Class"):

Public Class CompareByNameOfPlayer
   Implements IComparer(of Player)

   Now press the Enter key and two things will happen:  The wavy blue line under the word IComparer will disappear and the outline of a new procedure will be automatically inserted into this new class.  (If necessary, also type in "End Class" to remove any remaining error message.)   The outline should look like this:

Public Function Compare(ByVal x As Player, ByVal y As Player) As Integer Implements System.Collections.Generic.IComparer(Of Player).Compare

End Function

      One of the useful features of using the Generics version of IComparer is that this Compare function knows that the comparison will be based on Player objects and so it uses the Player Type in the parameters shown there - x and y.   In the non-generics version, this would be of type Object and we would need to write more code in the function to cast from Object to Player before we could safely deal with it.  

    We will need to add some code to this skeleton procedure to give instructions on how we want it to sort. 

If x Is Nothing AndAlso y Is Nothing Then Return 0

If x Is Nothing And Not y Is Nothing Then Return 1

If Not x Is Nothing And y Is Nothing Then Return -1

Return String.Compare(x.Name, y.Name, True)

   These four lines are all that are needed to implement a valid Compare method that can be used by the compiler to sort collections of Player objects.   As you can see, the first three lines deal with cases where either or both the parameters are Nothing.     I'm not entirely convinced if these are necessary, bearing in mind that we have restricted our code to a strongly typed collection of Player objects, but it's habit I've got into pre-VB 2005 and it won't do any harm even if it does no good!

   The actual comparison of Names takes place in the last line.  Because we are dealing with a String (the Name Property is of type String) we can simply harness the String.Compare function and use this to compare two Names.  String.Compare will carry out the comparison for us  because it knows how to compare Strings alphabetically.   As you will see when we move on to the sort by Score procedure, we have to take a slightly longer way round.

   I have also included an additional parameter,  the IgnoreCase Boolean parameter which I have set to True.   This ensures that the comparisons will be made in a case insensitive manner - an approach that makes sense in this particular scenario.

   You can now try out this comparer and use it to sort the List of Players.   Put this code in the Windows Form:

PlayersList.Sort(New Player.CompareByNameOfPlayer) 
      For Each APlayer As Player In PlayersList
         ListBox1.Items.Add(String.Format("{0}, Current Score: {1}", APlayer.Name, APlayer.Score.ToString))

  Enter a few names and then click the button.  If all is well, the sorted Players List will appear.   You can add further names and click the button again to update the sort.   If your version isn't working as it should, check out the demo solution included with this article.

Sorting by Player.Score

  As I mentioned earlier, the procedure for creating a comparer to allow comparisons and sorting by the Players' Scores is similar.   Once again we will create a nested Class:

Public Class CompareByScore
Implements IComparer(Of Player)

Public Function Compare(ByVal x As Player, ByVal y As Player) As Integer _
Implements IComparer(Of Player).Compare

   If x Is Nothing AndAlso y Is Nothing Then Return 0
   If x Is Nothing And Not y Is Nothing Then Return 1
   If Not x Is Nothing And y Is Nothing Then Return -1
   If x.Score < y.Score Then
    Return -1
   ElseIf x.Score = y.Score Then
    Return 0
    Return 1
   End If
End Function
End Class

 This code is quite similar to the code we used for sorting by name, the difference being that we can't use the String.Compare method here because we are dealing with an Integer and not a String.    So you will see that last few lines of code manually compare the two values and return -1, 0 or 1 depending on which argument has the higher value.

   For testing purposes at this point you could add a second button to the form and put code in its Click event similar to the code we used in the last section when sorting by Name. i.e.

PlayersList.Sort(New Player.CompareByScore) 
      For Each APlayer As Player In PlayersList
         ListBox1.Items.Add(String.Format("{0}, Current Score: {1}", APlayer.Name, APlayer.Score.ToString))

   I have highlighted the only change that you need to make to call this variation of the sort.

   In reality, you probably won't want a separate button for each kind of sort and it's very common to use a set of Radio Buttons to allow a user to select one setting from a choice of two or more mutually exclusive options.  I won't delve into that here, but you can see a sample in the demo Solution that is included with this article.

Summary (So Far)

  So by this stage we have seen how to create a simple class that will allow us to build objects.  We saw how to create collections of those objects and here we have discussed how to sort those collections in various ways.    None of the code is very complex and you can use the same steps to create classes and sortable collections of your own.





Saving Collections of Objects

Saving Collections of Objects

    In many situations where you are dealing with collections of objects, you find sooner or later that you need some way of storing (or persisting) the data, usually to and from a file on the hard drive.  

 You have probably already had some experience of writing to files and reading back from them using some of the many and varied  basic System.IO methods that are available.   These are excellent when you want to save blocks of text, for instance, and read them back when needed.   But when it comes to saving collections of custom objects, such as the Player objects we are dealing with in this article, there are better ways.

   So, although it is perfectly possible to write and read the data needed to store Player objects, including the values assigned to properties for individual instances, it can become long-winded, particularly if the number of properties begins to increase.   What we will look at in this section is using Serialization and Deserialization for the task.


   Serialization is essentially a way of breaking down the data into a series or stream of individual bytes that can then be sent to some repository such as a file on a hard drive.   Serialization and Deserialization sound like quite complex topics (and they can be), but we don't need to dig very deeply into the technology in order to be able to use it for our purposes here.

  The first thing we need to do is to inform the compiler that our class is Serializable.   This is very simply done -  just place the Serializeable Attribute <SERIALIZABLE> in front of the declaration line of the Player Class,  i.e.:

<SERIALIZABLE()>Public Class Player

   Now we can write code that will serialize individual Player instances or - more usefully - collections of them, to a file.  For the purposes of this article I am going to hard-code the file path and also write procedures that are specific for our Player Class.   Just bear in mind that for longer term use it is very easy to use the same general approach to write generic procedures that can be used for other kinds of class objects.

  Firstly you will need to add two more Imports statements to the form file:

Imports System.IO
Imports System.Runtime.Serialization.Formatters.Binary

   Also it will be helpful to have a variable to hold the path and name of the file that we plan to read to and write from.  Because this variable will need to be accessed from more than one separate procedure (e.g. one that reads from file and one that writes) this variable should be placed near the the top of the form code, outside any procedures:

Dim DataFile As String = Application.StartupPath & "\PlayerData.BIN"

 Next you need the following code which you can place, for example, in a Button Click event on the form.

  ' Create a FileStream
Using FStrm As New FileStream(DataFile, FileMode.OpenOrCreate)
  ' A BinaryFormatter
  Dim BinForm As New BinaryFormatter
  ' Serialize the data using the BinaryFormatter
  ' --> FileStream --> File

  BinForm.Serialize(FStrm, PlayersList)
End Using

  The "Using" statement is new to VB 2005 and is a handy shorthand way of ensuring that the filestream is released and disposed as soon as it has done its work.   Prior to VB 2005, this was something you would have handled in code with Close and Dispose instructions.  It isn't strictly necessary in all cases but is worth including in your code whenever you are working with Streams.



  This code creates a FileStream (which in its simplest terms you can think of as a kind of conduit from your application to the hard drive). The file path and the instruction to open or create that particular file is passed to this FileStream.

  A Binary Formatter is required next in order to "translate" the data (Serialize it ) in binary form to pass it via theFileStream to the hard drive storage. As you can see, the name of the FileStream and the source data - the list of Players - is passed to the Binary Formatter.

  In the demonstration project I have cleared all the data from PlayersList as the next step. This is probably not something you would usually do, but I've done that in the demo so that you can easily test that the next stage - that is reading data back from the file - works properly.

Deserializing Data From The File
  The reverse process, i.e. getting data back from the file and storing it in the PlayersList is broadly similar.  Here is the code (which you may want to put in the click event of another button):

' Create a FileStream
Using FStrm As New FileStream(DataFile, FileMode.Open)
Dim BinForm As New BinaryFormatter
' Deserialize the data using the BinaryFormatter
PlayersList = DirectCast(BinForm.Deserialize(FStrm), List(Of Player))
End Using

' Redisplay the data from file
ListBox1.Items.Add("Data received from file:- ")

For Each APlayer As Player In PlayersList
 ListBox1.Items.Add(String.Format("{0}, Current Score: {1}", APlayer.Name, APlayer.Score.ToString))

How It Works
The FileStream creation process is more or less the same as that we have used previously. For obvious reasons though, the FileMode used is FileMode.Open as we need to access a currently existing file and not create a new empty one.
(Just as a side note, you'll see that the demo solution also includes a check that the file does actually exist - always a wise precaution. )

  The BinaryFormatter this time Deserializes the data. The rather complicated looking line:-
PlayersList = DirectCast(BinForm.Deserialize(FStrm), List(Of Player))
  feeds the data directly into PlayersList.   So essentially that line is saying "Fill the PlayersList by deserializing everything you find via the FileStream, turning it into the format that the Player Class uses."

The remaining lines simply display the read data in the listbox.

In this article you have seen how to create a class, a collection, sort the collection, save data to a file and read it back in the correct format.   With relatively very few lines of code in total you have some very powerful tools - the last section, Serialization and Deserializaton especially make very easy work of what would otherwise be a complicated and fiddly task.

   You can use this approach in projects of your own and I hope that this article has provided you with a clear explanation of the steps involved, should you wish to do so.   As mentioned earlier, there is a downloadable demonstration Solution, which includes all the code discussed in this article, plus some additional validation and user interface features.