University of Oxford
OOP using VB.NET
|Author: Barry Cornelius Date: 23rd October 2004; first created: 18th October 2004|
|size||C#||Managed C++||Visual Basic.NET|
|System.Int32||32||int||int or long||Integer|
One of the significant features of the .NET Framework is that it comes with an enormous class library called the Framework Class Library (FCL). The types provided by the FCL can be used in any .NET language.
System.Object System.ValueType System.String System.IComparable System.IEnumerable System.Collections.ArrayList System.Collections.Hashtable System.Collections.IEnumerator
System.Windows.Forms.Form System.Windows.Forms.Application System.Windows.Forms.Button System.Windows.Forms.TextBox System.Windows.Forms.Label System.EventArgs System.EventHandler System.Drawing.Point System.Drawing.Size System.Windows.Forms.MainMenu System.Windows.Forms.MenuItem
Note: At the present time, there are two .NET Framework downloads: there is a .NET Framework Redistributable which software developers can give away with their products, and there is also a .NET Framework SDK which includes the compilers for Visual Basic.NET and C#. Although both of these downloads can currently be downloaded for free, I am unclear as to whether both will form part of future versions of the Windows operating system.
So you just need the .NET Framework in order to compile and execute Visual Basic.NET programs or C# programs. The compilers are in files called vbc.exe and csc.exe. On my laptop these files are in the directory:
Then use your favourite text editor to produce a program in VB.NET. For example, suppose you use a text editor to produce a file called Module1.vb that contains the following text. Note: ignore the line numbers that appear in this document: they do not form part of the programs:
0001: Imports System 0002: Module Module1 0003: 0004: Sub Main() 0005: Console.Write("Centigrade value: ") 0006: Dim tCentigradeString As String = Console.ReadLine() 0007: Dim tCentigrade As Double = Double.Parse(tCentigradeString) 0008: Dim tFahrenheit As Double = 32 + tCentigrade * 9 / 5 0009: Console.WriteLine("Fahrenheit value: " & tFahrenheit) 0010: End Sub 0011: 0012: End Module
The program given above includes four calls of methods. Two of these are calls of subroutines: these are the calls of Console.Write and Console.WriteLine. The other two calls are calls of functions: these are the calls of Console.ReadLine and Double.Parse.
Related methods can be grouped together using a class (or a structure). For example, the class Console contains methods associated with the keyboard/screen. You can deduce from the above program that it has methods called Write, WriteLine and ReadLine. And you can see that the structure Double has a method called Parse.
Double x = 2.0; Double y = Math.Sqrt(x);
ClassOrStructureName.MethodName(parameters)In Visual Basic.NET, such methods are called shared methods: elsewhere they are called static methods or class methods. There is nothing new here: since the beginning of computing, we have had libraries of methods. So, shared methods are not exciting, and they have nothing to do with object-oriented programming.
One approach to writing a program is to identify the real-world objects (of the problem) that you need to represent in the program. Each real-world object can be in a number of states (i.e., may possess one of a number of values) and has a set of operations that can be performed on it.
Some of these real-world objects can be represented in a program by variables each of which is declared to be one of the simple types. For example, if our problem needs to represent the number of students in a lecture theatre, we can use an Integer in a VB.NET program in order to represent this real-world object. Or we might use a variable of type Double in order to represent the temperature of the air outside.
However, there are many real-world objects that cannot be represented in a VB.NET program by a value that is of a simple type. For example, we might want to represent a date in history, a point in two-dimensional space, the ISBN of a book, an e-mail address, the URL of a WWW page, and so on.
In most programming languages, a programmer can introduce new types to model these real-world objects. One way of doing this in a .NET language is to introduce a class declaration: we could introduce a class declaration for each of these classes of real-world objects.
However, we need not get into the details of writing class declarations straightaway: any .NET language can use any of the large number of classes defined in the Framework Class Library: maybe the FCL has a class that we can use.
Suppose you want to store a collection of strings. One way of doing this is to use a class of the FCL called ArrayList. This class allows you to create a flexible array of objects, an array which increases in size whenever necessary.
0016: Dim tLanguages As ArrayList = New ArrayList(2)
This makes the variable tLanguages point to an ArrayList object. It is a bit silly to give the ArrayList a capacity of 2, but this silliness will demonstrate that the ArrayList automatically increases in size when necessary.
0017: tLanguages.Add("Visual_Basic") 0018: tLanguages.Add("C#") 0019: tLanguages.Add("Managed_C++") 0020: tLanguages.Add("JScript")
These calls of Add are unlike the calls of Write, WriteLine, Readline and Parse. Because, here before the dot, we put the name of the object to which we want the Add method applied. We might have several ArrayList objects in our program: however, in these statements, we want the Add method applied to the tLanguages object.
0029: Sub OutputStringList(ByVal pArrayList As ArrayList) 0030: For Each tString As String In pArrayList 0031: Console.Write(tString & " ") 0032: Next 0033: Console.WriteLine() 0034: End Sub
Visual_Basic C# Managed_C++ JScript
JScript Managed_C++ C# Visual_Basic
0024: tLanguages.Insert(2, "J#") 0025: OutputStringList(tLanguages) 0026: tLanguages.Sort() 0027: OutputStringList(tLanguages)
JScript Managed_C++ J# C# Visual_Basic C# J# JScript Managed_C++ Visual_Basic
So far we have used Console, Double, String and ArrayList. (Three of these types are classes; the other one is a structure.) Unless the organisation of names is more complicated, we would quickly run out of names. To avoid name clashes, each name is allocated to a namespace.
Dim tCentigradeString As System.String = System.Console.ReadLine()
Dim tLanguages As System.Collections.ArrayList = New System.Collections.ArrayList(2)
Imports System Imports System.Collections Module Module1
Imports System Imports System.Collections Imports System.Data Imports System.Diagnostics Imports System.Drawing Imports System.Windows.Form Imports Microsoft.VisualBasic
We cannot just rely on others to produce all the types we need: we have to do some of this work ourselves. This is because some of the real-world objects that we will want to represent will be specific to whatever we are doing. We may need to represent people, bank accounts, grid references, ISBNs, audio CDs, and so on. And there are no types in the FCL for any of these.
In .NET languages, there are two kinds of value types: structure types and enumeration types; and there are four kinds of reference types: class types, interface types, array types and delegate types.
When we design a class declaration, we should think long-term: not about what we need in our current program but about what would be useful in this class so that it could also be used by other programs in the future.
0037: Public Class Point 0038: Private iX As Integer 0039: Private iY As Integer 0040: Public Sub New(ByVal pX As Integer, ByVal pY As Integer) 0041: iX = pX 0042: iY = pY 0043: End Sub 0044: Public Property X() As Integer 0045: Get 0046: Return iX 0047: End Get 0048: Set(ByVal Value As Integer) 0049: iX = Value 0050: End Set 0051: End Property 0052: Public Function Distance() As Double 0053: Return Math.Sqrt(iX * iX + iY * iY) 0054: End Function 0055: Public Overloads Overrides Function Equals(ByVal pObject As Object) As Boolean 0056: If (pObject Is Nothing Or Not (Me.GetType() Is pObject.GetType())) Then 0057: Return False 0058: End If 0059: Dim tPoint As Point = DirectCast(pObject, Point) 0060: Return Me.iX = tPoint.iX And Me.iY = tPoint.iY 0061: End Function 0062: Public Overrides Function ToString() As String 0063: Return iX & ":" & iY 0064: End Function 0065: End Class
In Visual Studio.NET, you add a new class (called Point) to a project by selecting Add New Item from the File menu, selecting the Class icon, changing the contents of the textbox to Point.vb, and clicking on Open.
0068: Dim tPoint As Point = New Point(100, 200)
0069: Dim tPointString As String = tPoint.ToString() 0070: Console.WriteLine(tPointString)
0072: Dim tDistance As Double = tPoint.Distance() 0073: Console.WriteLine(tDistance)
0074: Dim tAnotherPoint As Point = tPoint 0075: Console.WriteLine(tAnotherPoint)
0076: Console.WriteLine("Is: " & (tPoint Is tAnotherPoint))
0077: Console.WriteLine("Equals: " & (tPoint.Equals(tAnotherPoint)))
0078: Dim tClonePoint As Point = New Point(100, 200) 0079: Console.WriteLine("Is: " & (tPoint Is tClonePoint)) 0080: Console.WriteLine("Equals: " & (tPoint.Equals(tClonePoint)))
Is: False Equals: True
0081: tAnotherPoint.X = 42 0082: Console.WriteLine(tPoint.X)
Note: in the above declaration of the Point class, the iX and iY fields have been marked as Private. This means that a client of the class Point (such as Module1) is not allowed to access the iX and iY fields of any object. Indeed, if Module1 contains a statement like:
tAnotherPoint.iX = 42it will not compile.
Instead, if it is necessary to allow a client to inspect/alter the value of a field, the class should provide a Property. In the above declaration of the Point class, a Property called X has been introduced that permits the value of the iX field to be got or set. By using a Property, you know that all accesses to the field will be done through the code of the Property. So you retain control. This is useful, e.g.:
0083: tClonePoint.X = 27 0084: Console.WriteLine(tPoint.X)
In C#, structure types are called structs. Chapter 11 of the 'C# Language Specification' says ‘Structs are particularly useful for small data structures that have value semantics. Complex numbers, points in a coordinate system, or key-value pairs in a dictionary are good examples of structs.’ So, if we want to represent points in 2D space in a program, it would probably be more appropriate to use a structure type rather than a class type.
0087: Public Structure Point 0088: Private iX As Integer 0089: Private iY As Integer 0090: Public Sub New(ByVal pX As Integer, ByVal pY As Integer) 0091: iX = pX 0092: iY = pY 0093: End Sub 0094: Public Property X() As Integer 0095: Get 0096: Return iX 0097: End Get 0098: Set(ByVal Value As Integer) 0099: iX = Value 0100: End Set 0101: End Property 0102: Public Function Distance() As Double 0103: Return Math.Sqrt(iX * iX + iY * iY) 0104: End Function 0105: Public Overrides Function ToString() As String 0106: Return iX & ":" & iY 0107: End Function 0108: End Structure
0111: Dim tPoint As Point = New Point(100, 200)
0117: Dim tAnotherPoint As Point = tPoint
As well as declaring our own value types (such as the structure type Point given in the previous section), there are hundreds of value types in the FCL. Some examples of enumeration types (from the FCL) are:
System.DayOfWeek System.IO.FileMode System.Drawing.Drawing2D.DashStyle System.Drawing.Drawing2D.FillMode System.Globalization.DateTimeStyles System.Windows.Forms.ButtonBorderStyle
System.DateTime System.TimeSpan System.Drawing.Color System.Drawing.Point System.Drawing.Rectangle System.Drawing.Size
One area where bugs often occur in programming is where variables are not typed or values are converted from one type to another. In VB.NET, it is possible to insist that all variables are typed and possible unsafe conversions are documented using DirectCast (or CType). This is done by setting the Option Strict to On.
At http://www.eggheadcafe.com/articles/20030801.asp, it says: ‘Unfortunately, in bringing out the final release of Visual Studio.NET, Microsoft bowed to the cries of millions of VB programmers and left Option Strict Off by default in all our VB.NET projects! Just about every professional author and programmer will tell you that you should ALWAYS set Option Strict to "On"!’
‘Fortunately, there is a very easy way to do this so that you won't forget. You are hereby strongly encouraged to go to the Visual Studio's Tools | Options menu, click the Projects | VB Defaults folder and set Option Strict and Option Explicit to On. You might then spend a bit more time writing CType-s or DirectCast-s, but you will avoid spending much more time tracking down some mysterious runtime errors. Many of your programs will also run FASTER.’
That does not seem to work for projects that you have already created. For such projects, do the following: From the Project menu, choose the last option which is the name of the project followed by the word Properties (e.g., PointClassTest Properties). Then click on Build. Then change the setting of "Option Strict" to "On".
Standalone programs that read and/or write to a command shell window are boring: over the years, Visual Basic has led the way in making it easier to produce programs that have a graphical user interface (GUI).
Having added the GUI components to the Form, you can double click each button in turn. When you double click a button, Visual Studio.NET will open up the code of the Form1.vb file positioning you in the midst of the method responsible for handling clicks of that button. So if you double click the first button, it will put you in the body of a method called Button1_Click. You can then add the code you want executed when that button is clicked.
0128: Public Class Form1 0129: Inherits System.Windows.Forms.Form 0130: 0131: #Region " Windows Form Designer generated code " 0132: 0133: Public Sub New() 0134: MyBase.New() 0135: 0136: 'This call is required by the Windows Form Designer. 0137: InitializeComponent() 0138: 0139: 'Add any initialization after the InitializeComponent() call 0140: 0141: End Sub
0212: #End Region 0213: 0214: Private Sub Button1_Click(ByVal sender As System.Object, _ 0215: ByVal e As System.EventArgs) Handles Button1.Click 0216: Label1.Text = TextBox1.Text + " has arrived" 0217: End Sub 0218: 0219: Private Sub Button2_Click(ByVal sender As System.Object, _ 0220: ByVal e As System.EventArgs) Handles Button2.Click 0221: Label1.Text = TextBox1.Text + " has departed" 0222: End Sub 0223: 0224: End Class
When we are using Visual Studio.NET, a lot of this code is hidden from us: on the screen, it is in the region labelled Windows Form Designer generated code. You can click on the + button in order to reveal the contents of this region.
You should be careful about altering the contents of this region. If you change the GUI components of the Form, it will automatically change the code of this region. Similarly, if you are clever enough to edit the code of the region to add the code for a new GUI component and it understands what you have done, it will automatically alter the contents of the Form.
0225: Public Class Form1 0226: Inherits System.Windows.Forms.Form 0227: 0228: Private iArrayList As ArrayList 0229: #Region " Windows Form Designer generated code "
0231: Public Sub New() 0232: MyBase.New() 0233: 0234: 'This call is required by the Windows Form Designer. 0235: InitializeComponent() 0236: 0237: 'Add any initialization after the InitializeComponent() call 0238: iArrayList = New ArrayList 0239: End Sub
0335: Private Sub Button1_Click(ByVal sender As System.Object, _ 0336: ByVal e As System.EventArgs) Handles Button1.Click 0337: iArrayList.Add(TextBox1.Text) 0338: Label1.Text = TextBox1.Text + " has arrived" 0339: End Sub 0340: 0341: Private Sub Button2_Click(ByVal sender As System.Object, _ 0342: ByVal e As System.EventArgs) Handles Button2.Click 0343: iArrayList.Remove(TextBox1.Text) 0344: Label1.Text = TextBox1.Text + " has departed" 0345: End Sub 0346: 0347: Private Sub Button3_Click(ByVal sender As System.Object, _ 0348: ByVal e As System.EventArgs) Handles Button3.Click 0349: TextBox2.Clear() 0350: For Each tString As String In iArrayList 0351: TextBox2.AppendText(tString & vbCrLf) 0352: Next 0353: End Sub 0354: End Class
The problem with the above solution is that the code handling the carpark is mixed up with the code handling the GUI. If there is a bug with the code for the carpark, then it is difficult to find the code because of all the code to do with the GUI.
0355: Imports System.Text 0356: Public Class CarPark 0357: Private iArrayList As ArrayList 0358: Public Sub New() 0359: iArrayList = New ArrayList 0360: End Sub 0361: Public Sub Arrive(ByVal pString As String) 0362: iArrayList.Add(pString) 0363: End Sub 0364: Public Sub Depart(ByVal pString As String) 0365: iArrayList.Remove(pString) 0366: End Sub 0367: Public Overrides Function ToString() As String 0368: Dim tStringBuilder As StringBuilder = New StringBuilder 0369: For Each tString As String In iArrayList 0370: tStringBuilder.Append(tString & vbCrLf) 0371: Next 0372: Return tStringBuilder.ToString() 0373: End Function 0374: Public Function Size() As Integer 0375: Return iArrayList.Count 0376: End Function 0377: End Class
0378: Public Class Form1 0379: Inherits System.Windows.Forms.Form 0380: 0381: Private iCarPark As CarPark 0382: #Region " Windows Form Designer generated code " 0383: 0384: Public Sub New() 0385: MyBase.New() 0386: 0387: 'This call is required by the Windows Form Designer. 0388: InitializeComponent() 0389: 0390: 'Add any initialization after the InitializeComponent() call 0391: iCarPark = New CarPark 0392: End Sub
0488: Private Sub Button1_Click(ByVal sender As System.Object, _ 0489: ByVal e As System.EventArgs) Handles Button1.Click 0490: iCarPark.Arrive(TextBox1.Text) 0491: Label1.Text = TextBox1.Text + " has arrived" 0492: End Sub 0493: 0494: Private Sub Button2_Click(ByVal sender As System.Object, _ 0495: ByVal e As System.EventArgs) Handles Button2.Click 0496: iCarPark.Depart(TextBox1.Text) 0497: Label1.Text = TextBox1.Text + " has departed" 0498: End Sub 0499: 0500: Private Sub Button3_Click(ByVal sender As System.Object, _ 0501: ByVal e As System.EventArgs) Handles Button3.Click 0502: TextBox2.Text = iCarPark.ToString() 0503: End Sub 0504: End Class
By doing this, no longer is the code of the GUI mixed up with the code of the carpark. Indeed, it would now be easier to create several carparks: we would not have to duplicate the code for adding to the ArrayList or for visiting each element of the ArrayList, and so on.
The class declaration for CarPark hides an ArrayList and provides a number of methods for clients to access the ArrayList indirectly. This idea of hiding fields behind access methods is called information hiding or data encapsulation.
It may seem like a complicated way of providing an object and accessing it. However, you should view the class declaration as documenting a design decision. At the moment, we have chosen to represent a carpark by an ArrayList. At a later stage, we may feel that this is wrong: we may want to use some other representation. Since we have retained control over the access to the field of the class by making it private and providing access methods, we can make changes like this with only minimal impact to the code of the program: we know that the only code that needs to be changed is located in the methods of the CarPark class.
To demonstrate this, here is a different version of the CarPark class: in this version, the events are recorded in a MySQL database. The important thing to notice that the only code that needs to be changed is the code of the CarPark class: there are no changes to the Form1 class.
0505: Imports System.Text 0506: Imports System.Data.Odbc 0507: Public Class CarPark 0508: Private iConnectionString As String = _ 0509: "DSN=Pdcl0bjc_CarParkVB;UID=dcl0bjc;PWD=XXXXXXX;" 0510: Public Sub New() 0511: Console.WriteLine("CarPark New") 0512: End Sub 0513: Public Sub Arrive(ByVal pString As String) 0514: Dim tSQLString As String = _ 0515: "INSERT INTO vehicles SET number = '" + pString + "'" 0516: Console.WriteLine(tSQLString) 0517: Dim tOdbcConnection As OdbcConnection = _ 0518: New OdbcConnection(iConnectionString) 0519: Dim tOdbcCommand As OdbcCommand = _ 0520: New OdbcCommand(tSQLString, tOdbcConnection) 0521: Try 0522: tOdbcConnection.Open() 0523: tOdbcCommand.ExecuteNonQuery() 0524: tOdbcConnection.Close() 0525: Catch pException As Exception 0526: MessageBox.Show(pException.Message) 0527: End Try 0528: End Sub 0529: Public Sub Depart(ByVal pString As String) 0530: Dim tSQLString As String = _ 0531: "DELETE FROM vehicles WHERE number = '" + pString + "'" 0532: Console.WriteLine(tSQLString) 0533: Dim tOdbcConnection As OdbcConnection = _ 0534: New OdbcConnection(iConnectionString) 0535: Dim tOdbcCommand As OdbcCommand = _ 0536: New OdbcCommand(tSQLString, tOdbcConnection) 0537: Try 0538: tOdbcConnection.Open() 0539: tOdbcCommand.ExecuteNonQuery() 0540: tOdbcConnection.Close() 0541: Catch pException As Exception 0542: MessageBox.Show(pException.Message) 0543: End Try 0544: End Sub 0545: Public Overrides Function ToString() As String 0546: Dim tStringBuilder As StringBuilder = New StringBuilder 0547: Dim tSQLString As String = "SELECT number FROM vehicles" 0548: Console.WriteLine(tSQLString) 0549: Dim tOdbcConnection As OdbcConnection = _ 0550: New OdbcConnection(iConnectionString) 0551: Dim tOdbcCommand As OdbcCommand = _ 0552: New OdbcCommand(tSQLString, tOdbcConnection) 0553: Try 0554: tOdbcConnection.Open() 0555: Dim tOdbcDataReader As OdbcDataReader = _ 0556: tOdbcCommand.ExecuteReader(CommandBehavior.SequentialAccess) 0557: While (tOdbcDataReader.Read()) 0558: tStringBuilder.Append(tOdbcDataReader.GetString(0) & vbCrLf) 0559: End While 0560: tOdbcConnection.Close() 0561: Catch pException As Exception 0562: MessageBox.Show(pException.Message) 0563: End Try 0564: Return tStringBuilder.ToString() 0565: End Function 0566: Public Function Size() As Integer 0567: Dim tNumRows As Integer = 0 0568: Dim tSQLString As String = "SELECT number FROM vehicles" 0569: Console.WriteLine(tSQLString) 0570: Dim tOdbcConnection As OdbcConnection = _ 0571: New OdbcConnection(iConnectionString) 0572: Dim tOdbcCommand As OdbcCommand = _ 0573: New OdbcCommand(tSQLString, tOdbcConnection) 0574: Try 0575: tOdbcConnection.Open() 0576: Dim tOdbcDataReader As OdbcDataReader = _ 0577: tOdbcCommand.ExecuteReader(CommandBehavior.SequentialAccess) 0578: While (tOdbcDataReader.Read()) 0579: tNumRows += 1 0580: End While 0581: tOdbcConnection.Close() 0582: Catch pException As Exception 0583: MessageBox.Show(pException.Message) 0584: End Try 0585: Return tNumRows 0586: End Function 0587: End Class
The above code uses the Open Database Connectivity .NET Data Provider. In order to use ODBC, you will need to install an ODBC driver. As the above code connects to a MySQL server, I installed the MyODBC driver, onto the laptop from which I was running VB.NET.
One book on Visual Basic.NET is 'Visual Basic.NET for Dummies' by Richard Mansfield (Wiley, 2003, 0-7645-2579-4). The Appendix of this book gives a useful summary of the issues involved in moving from Visual Basic 6 to Visual Basic.NET.