3. Classes, structures, interfaces
3.1. Objects by example
3.1.1. General Overview
We will now explore object-oriented programming through examples. An object is an entity that contains data defining its state (called properties) and functions (called methods). An object is created based on a template called a class:
Public Class c1
' attributes
Private p1 As type1
Private p2 As type2
....
' method
Public Sub m1(....)
...
End Sub
' method
Public Function m2(...)
....
End Function
End Class
From the previous class C1, we can create many objects O1, O2,… All will have the properties p1, p2,… and the methods m3, m4, … But they will have different values for their properties pi, thus each having its own state. By analogy, the declaration
creates two objects (the term is incorrect here) of type (class) Integer. Their only property is their value. If O1 is an object of type C1, O1.p1 refers to the property p1 of O1 and O1.m1 to the method m1 of O1. Let’s consider a first object model: the Person class.
3.1.2. Definition of the Person class
The definition of the Person class will be as follows:
Public Class Person
' attributes
Private firstName As String
Private lastName As String
Private age As Integer
' method
Public Sub Initialize(ByVal FirstName As String, ByVal LastName As String, ByVal Age As Integer)
Me.firstName = P
Me.lastName = N
Me.age = age
End Sub
' method
Public Sub identify()
Console.Out.WriteLine((firstName & "," & lastName & "," & age))
End Sub
End Class
Here we have the definition of a class, which is a data type. When we create variables of this type, we call them objects or class instances. A class is therefore a template from which objects are constructed. The members or fields of a class can be data (attributes), methods (functions), or properties. Properties are special methods used to retrieve or set the value of an object’s attributes. These fields can be accompanied by one of the following three keywords:
A private field is accessible only by the class’s internal methods | |
A public field is accessible by any function, whether or not it is defined within the class | |
A protected field is accessible only by the class's internal methods or by a derived object (see the concept of inheritance later). |
Generally, a class’s data is declared private, while its methods and properties are declared public. This means that the user of an object (the programmer):
- will not have direct access to the object’s private data
- can call the object’s public methods, particularly those that provide access to its private data.
The syntax for declaring a class is as follows:
public class class
private data or method or private property
public public data, method, or property
protected protected data, method, or property
end class
The order in which private, protected, and public attributes are declared is arbitrary.
3.1.3. The initialize method
Let’s return to our [person] class declared as:
Public Class person
' attributes
Private firstName As String
Private lastName As String
Private age As Integer
' method
Public Sub Initialize(ByVal FirstName As String, ByVal LastName As String, ByVal Age As Integer)
Me.firstName = P
Me.lastName = N
Me.age = age
End Sub
' method
Public Sub identify()
Console.Out.WriteLine((firstName & "," & lastName & "," & age))
End Sub
End Class
What is the role of the initialize method? Because lastName, firstName, and age are private data of the Person class, the statements:
are invalid. We must initialize an object of type Person via a public method. This is the role of the initialize method. We will write:
The syntax p1.initialize is valid because initialize is public.
3.1.4. The new operator
The sequence of statements
is incorrect. The statement
declares p1 as a reference to an object of type person. This object does not yet exist, so p1 is not initialized. It is as if we were writing:
where we explicitly indicate with the keyword nothing that the variable p1 does not yet reference any object. When we then write
we call the initialize method of the object referenced by p1. However, this object does not yet exist, and the compiler will report an error. To make p1 reference an object, you must write:
This creates an uninitialized Person object: the attributes name and first_name, which are references to String objects, will have the value nothing, and age will have the value 0. There is therefore a default initialization. Now that p1 references an object, the initialization statement for this object
is valid.
3.1.5. The keyword Me
Let's look at the code for the *Initialize* method:
Public Sub initialize(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.lastName = P
Me.lastName = N
Me.age = age
End Sub
The statement Me.firstName = P means that the firstName property of the current object (Me) is assigned the value P. The keyword Me refers to the current object: the one in which the method is being executed. How do we know this? Let’s look at how the object referenced by p1 is initialized in the calling program:
It is the initialize method of the p1 object that is called. When the Me object is referenced within this method, it actually refers to the p1 object. The initialize method could also have been written as follows:
Public Sub initialize(ByVal P As String, ByVal N As String, ByVal age As Integer)
firstName = P
lastName = N
Me.age = age
End Sub
When a method of an object references an attribute A of that object, the notation Me.A is implied. It must be used explicitly when there is a conflict of identifiers. This is the case with the statement:
Me.age = age;
where age refers to both an attribute of the current object and the age parameter received by the method. The ambiguity must then be resolved by referring to the age attribute as Me.age.
3.1.6. A test program
Here is a short test program:
Public Class Person
' attributes
Private firstName As String
Private lastName As String
Private age As Integer
' method
Public Sub Initialize(ByVal FirstName As String, ByVal LastName As String, ByVal Age As Integer)
Me.firstName = P
Me.lastName = N
Me.age = age
End Sub
' method
Public Sub identify()
Console.Out.WriteLine((firstName & "," & lastName & "," & age))
End Sub
End Class
and the results obtained:
dos>vbc person1.vb
Microsoft (R) Visual Basic .NET Compiler version 7.10.3052.4 for Microsoft (R) .NET Framework version 1.1.4322.573
3.1.7. Using a compiled class file (assembly)
Note that in the previous example, there are two classes in our test program: the person and test1 classes. There is another way to proceed:
-
Compile the Person class into a separate file called an assembly. This file has a .dll extension
-
We compile the test1 class by referencing the assembly that contains the person class.
The two source files become the following:
test.vb | |
person2.vb | |
The Person class is compiled by the following statement:
dos>vbc /t:library person2.vb
Microsoft (R) Visual Basic .NET Compiler version 7.10.3052.4 for Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
02/24/2004 4:50 PM 509 person2.vb
02/24/2004 4:49 PM 143 test.vb
02/24/2004 4:50 PM 3,584 person2.dll
The compilation produced a file named personne2.dll. It is the /t:library compilation option that specifies the creation of an "assembly" file. Now let's compile the test.vb file:
dos>vbc /r:personne2.dll test.vb
Microsoft® Visual Basic .NET Compiler version 7.10.3052.4 for Microsoft® .NET Framework version 1.1.4322.573
dos>dir
02/24/2004 4:50 PM 509 person2.vb
02/24/2004 4:49 PM 143 test.vb
02/24/2004 4:50 PM 3,584 person2.dll
02/24/2004 4:51 PM 3,072 test.exe
The /r:personne2.dll compilation option tells the compiler that it will find certain classes in the personne2.dll file. When it finds a reference to the Person class in the source file test.vb—a class not declared in the source file test.vb—it will search for the Person class in the .dll files referenced by the /r option. It will find the Person class here in the Person2.dll assembly. We could have included other classes in this assembly. To use multiple compiled class files during compilation, we would write:
Running the program test1.exe produces the following results:
3.1.8. Another method initializes
Let’s continue with the Person class and add the following method to it:
' method
Public Sub initialize(ByVal P As Person)
firstName = P.firstName
lastName = P.lastName
Me.age = P.age
End Sub
We now have two methods named Initialize: this is valid as long as they accept different parameters. That is the case here. The parameter is now a reference P to a Person. The attributes of the Person P are then assigned to the current object (Me). Note that the initialize method has direct access to the attributes of object P even though they are of type private. This is always true: an object O1 of class C always has access to the attributes of objects of the same class C. Here is a test of the new Person class, which has been compiled into Person.dll as explained previously:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
Module test1
Sub Main()
Dim p1 As New Person
p1.Initialize("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identify()
Dim p2 As New Person
p2.initialize(p1)
Console.Out.Write("p2=")
p2.Identify()
End Sub
End Module
and its results:
3.1.9. Constructors of the Person class
A constructor is a procedure named New that is called when the object is created. It is generally used to initialize the object. If a class has a constructor that accepts n arguments argi, the declaration and initialization of an object of that class can be done as follows:
dim object as class = new class(arg1, arg2, ... argn)
or
dim object as class
…
object = new Class(arg1, arg2, ... argn)
When a class has one or more constructors, one of these constructors must be used to create an object of that class. If a class C has no constructors, it has a default constructor, which is the constructor without parameters: public New(). The object’s attributes are then initialized with default values. This is what happened in the previous programs, where we wrote:
Let’s create two constructors for our Person class:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' the Person class
Public Class Person
' attributes
Private firstName As String
Private lastName As String
Private age As Integer
' constructors
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
initialize(P, N, age)
End Sub
Public Sub New(ByVal P As Person)
initialize(P)
End Sub
' object initialization methods
Public Sub Initialize(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.lastName = P
Me.lastName = N
Me.age = age
End Sub
Public Sub initialize(ByVal P As Person)
firstName = P.firstName
lastName = P.lastName
Me.age = P.age
End Sub
' method
Public Sub identify()
Console.Out.WriteLine((firstName & "," & lastName & "," & age))
End Sub
End Class
Both of our constructors simply call the corresponding initialize methods. Note that when, for example, the notation initialise(P) appears in a constructor, the compiler translates it to Me.initialise(P). In the constructor, the initialise method is therefore called to operate on the object referenced by Me, that is, the current object, the one being constructed. Here is a short test program:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test page
Module test
Sub Main()
Dim p1 As New Person("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identify()
Dim p2 As New Person(p1)
Console.Out.Write("p2=")
p2.Identify()
End Sub
End Module
and the results obtained:
3.1.10. Object references
We are still using the same Person class. The test program becomes the following:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test page
Module test
Sub Main()
' p1
Dim p1 As New Person("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.Identify()
' p2 references the same object as p1
Dim p2 As Person = p1
Console.Out.Write("p2=")
p2.Identify()
' p3 references an object that will be a copy of the object referenced by p1
Dim p3 As New Person(p1)
Console.Out.Write("p3=")
p3.Identify()
' we change the state of the object referenced by p1
p1.Initialize("Micheline", "Benoît", 67)
Console.Out.Write("p1=")
p1.identify()
' Since p2=p1, the object referenced by p2 must have changed state
Console.Out.Write("p2=")
p2.identify()
' since p3 does not reference the same object as p1, the object referenced by p3 must not have changed
Console.Out.Write("p3=")
p3.Identify()
End Sub
End Module
The results obtained are as follows:
p1=Jean,Dupont,30
p2=Jean,Dupont,30
p3=Jean,Dupont,30
p1=Micheline,Benoît,67
p2=Micheline,Benoît,67
p3=Jean,Dupont,30
When declaring the variable p1 with
p1 references the Person("Jean", "Dupont", 30) object but is not the object itself. In C, we would say that it is a pointer, i.e., the address of the created object. If we then write:
It is not the person("Jean","Dupont",30) object that is modified; it is the p1 reference that changes value. The person("Jean","Dupont",30) object will be "lost" if it is not referenced by any other variable.
When we write:
we initialize the pointer p2: it "points" to the same object (it refers to the same object) as the pointer p1. Thus, if we modify the object "pointed to" (or referenced) by p1, we modify the one referenced by p2.
When we write:
a new object is created, which is a copy of the object referenced by p1. This new object will be referenced by p3. If you modify the object "pointed to" (or referenced) by p1, you do not modify the one referenced by p3 in any way. This is what the results show.
3.1.11. Temporary objects
In an expression, you can explicitly call an object’s constructor: the object is created, but you cannot access it (to modify it, for example). This temporary object is created for the purpose of evaluating the expression and then discarded. The memory space it occupied will be automatically reclaimed later by a program called a "garbage collector," whose role is to reclaim memory space occupied by objects that are no longer referenced by program data. Consider the following new test program:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test page
Test Module
Sub Main()
Dim p As New Person(New Person("Jean", "Dupont", 30))
p.identify()
End Sub
End Module
and let's modify the constructors of the Person class so that they display a message:
' constructors
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Console.Out.WriteLine("Person constructor (String, String, Integer)")
Initialize(P, N, age)
End Sub
Public Sub New(ByVal P As Person)
Console.Out.WriteLine("Person constructor(Person)")
initialize(P)
End Sub
We get the following results:
showing the successive construction of the two temporary objects.
3.1.12. Methods for reading and writing private attributes
We add the necessary methods to the Person class to read or modify the state of the objects' attributes:
Imports System
Public Class Person
' attributes
Private firstName As [String]
Private lastName As [String]
Private age As Integer
' constructors
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Me.firstName = P
Me.lastName = N
Me.age = age
End Sub
Public Sub New(ByVal P As Person)
Me.firstName = P.firstName
Me.lastName = P.lastName
Me.age = P.age
End Sub
' Identify
Public Sub identify()
Console.Out.WriteLine((firstName + "," + lastName + "," + age))
End Sub
' accessors
Public Function getFirstName() As [String]
Return firstName
End Function
Public Function getLastName() As [String]
Return lastName
End Function
Public Function getAge() As Integer
Return age
End Function
'modifiers
Public Sub setFirstName(ByVal P As [String])
Me.firstName = P
End Sub
Public Sub setLastName(ByVal N As [String])
Me.lastName = N
End Sub
Public Sub setAge(ByVal age As Integer)
Me.age = age
End Sub
End Class
We test the new class with the following program:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test page
Public Module test
Sub Main()
Dim P As New Person("Jean", "Michelin", 34)
Console.Out.WriteLine(("P=(" & P.getFirstName() & "," & P.getLastName() & "," & P.getAge() & ")"))
P.setAge(56)
Console.Out.WriteLine(("P=(" & P.getFirstName() & "," & P.getLastName() & "," & P.getAge() & ")"))
End Sub
End Module
and we get the following results:
3.1.13. Properties
There is another way to access a class's attributes: by creating properties. These allow us to manipulate private attributes as if they were public. Consider the following Person class, where the previous getters and setters have been replaced by read-write properties:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' Person class
Public Class Person
' attributes
Private _firstName As [String]
Private _last_name As [String]
Private _age As Integer
' constructors
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Me._firstName = P
Me._last_name = N
Me._age = age
End Sub
Public Sub New(ByVal P As Person)
Me._first_name = P._first_name
Me._last_name = P._last_name
Me._age = P._age
End Sub
' Identify
Public Sub identify()
Console.Out.WriteLine((_firstName & "," & _lastName & "," & _age))
End Sub
' properties
Public Property firstName() As String
Get
Return _firstName
End Get
Set(ByVal Value As String)
_firstName = Value
End Set
End Property
Public Property lastName() As String
Get
Return _surname
End Get
Set(ByVal Value As String)
_name = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' Is age valid?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("Invalid age (" & Value & ")")
End If
End Set
End Property
End Class
A Property allows you to read (get) or set (set) the value of an attribute. In our example, we prefixed the attribute names with an underscore (_) so that the properties have the same names as the primitive attributes. This is because a property cannot have the same name as the attribute it manages; otherwise, there would be a naming conflict within the class. We have therefore named our attributes _firstName, _lastName, and _age and modified the constructors and methods accordingly. We then created three properties: lastName, firstName, and age. A property is declared as follows:
Public Property name() As Type
Get
...
End Get
Set(ByVal Value As Type)
...
End Set
End Property
where Type must be the type of the attribute managed by the property. It can have two methods called get and set. The get method is usually responsible for returning the value of the attribute it manages (it could return something else; nothing prevents it from doing so). The set method receives a parameter called value, which it normally assigns to the attribute it manages. It can use this opportunity to check the validity of the received value and, if necessary, throw an exception if the value is invalid. This is what is done here for the age.
How are these get and set methods called? Consider the following test program:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test page
Module test
Sub Main()
Dim P As New Person("Jean", "Michelin", 34)
Console.Out.WriteLine(("P=(" & P.firstName & "," & P.lastName & "," & P.age & ")"))
P.age = 56
Console.Out.WriteLine(("P=(" & P.firstName & "," & P.lastName & "," & P.age & ")"))
Try
P.age = -4
Catch ex As Exception
Console.Error.WriteLine(ex.Message)
End Try
End Sub
End Module
In the statement
we are trying to retrieve the values of the first_name, last_name, and age properties of the person P. It is the get method of these properties that is then called and returns the value of the attribute they manage.
In the statement
we want to set the value of the age property. The set method of this property is then called. It will receive 56 in its value parameter.
A property P of a class C that defines only the get method is said to be read-only. If c is an object of class C, the operation c.P=value will be rejected by the compiler.
Executing the previous test program yields the following results:
Properties therefore allow us to manipulate private attributes as if they were public.
3.1.14. Class methods and attributes
Suppose we want to count the number of [person] objects created in an application. We could manage a counter ourselves, but we risk forgetting temporary objects that are created here and there. It would seem safer to include an instruction in the [person] class constructors that increments a counter. The problem is passing a reference to this counter so that the constructor can increment it: we need to pass a new parameter to them. We can also include the counter in the class definition. Since it is an attribute of the class itself and not of a particular object of that class, we declare it differently using the Shared keyword:
To reference it, we write person._nbPeople to show that it is an attribute of the Person class itself. Here, we have created a private attribute that cannot be accessed directly from outside the class. We therefore create a public property to provide access to the class attribute nbPeople. To return the value of nbPeople, the Get method of this property does not need a specific Person object: indeed, _nbPeople is not the attribute of a specific object; it is the attribute of the entire class. Therefore, we need a property that is also declared as Shared:
which, from outside, will be called using the syntax person.nbPeople. The property is declared as read-only (ReadOnly) because it does not provide a set method. Here is an example. The Person class becomes the following:
Option Explicit On
Option Strict On
' namespaces
Imports System
' class
Public Class Person
' class attributes
Private Shared _nbPeople As Long = 0
' instance attributes
Private _firstName As [String]
Private _lastName As [String]
Private _age As Integer
' constructors
Public Sub New(ByVal FirstName As [String], ByVal LastName As [String], ByVal age As Integer)
' one more person
_nbPeople += 1
Me._firstName = P
Me._lastName = N
Me._age = age
End Sub
Public Sub New(ByVal P As Person)
' one more person
_nbPeople += 1
Me._firstName = P._firstName
Me._lastName = P._lastName
Me._age = P._age
End Sub
' identify
Public Sub identify()
Console.Out.WriteLine((_firstName & "," & _lastName & "," & _age))
End Sub
' class property
Public Shared ReadOnly Property nbPeople() As Long
Get
Return _nbPeople
End Get
End Property
' instance properties
Public Property firstName() As String
Get
Return _first_name
End Get
Set(ByVal Value As String)
_first_name = Value
End Set
End Property
Public Property lastName() As String
Get
Return _lastName
End Get
Set(ByVal Value As String)
_name = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' Is age valid?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("Invalid age (" & Value & ")")
End If
End Set
End Property
End Class
With the following program:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test page
Module test
Sub Main()
Dim p1 As New Person("Jean", "Dupont", 30)
Dim p2 As New Person(p1)
Console.Out.WriteLine(("Number of people created: " & person.nbPeople))
End Sub
End Module
The following results are obtained:
3.1.15. Passing an object to a function
We have already mentioned that, by default, VB.NET passes actual function parameters by value: the values of the actual parameters are copied into the formal parameters. When dealing with an object, do not be misled by the common linguistic convention of referring to an "object" rather than an "object reference." An object is manipulated only via a reference (a pointer) to it. What is therefore passed to a function is not the object itself but a reference to that object. It is thus the value of the reference—and not the value of the object itself—that is copied into the formal parameter: no new object is created. If an object reference R1 is passed to a function, it will be copied into the corresponding formal parameter R2. Thus, the references R2 and R1 point to the same object. If the function modifies the object pointed to by R2, it obviously modifies the one referenced by R1 since they are the same.
![]() |
This is illustrated by the following example:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test page
Module test
Sub Main()
' a person p1
Dim p1 As New Person("Jean", "Dupont", 30)
' Display p1
Console.Out.Write("Actual parameter before modification: ")
p1.Identify()
' Modify p1
modify(p1)
' Display p1
Console.Out.Write("Actual parameter after modification: ")
p1.Identify()
End Sub
Sub modify(ByVal P As Person)
' Display person P
Console.Out.Write("Formal parameter before modification: ")
P.identify()
' Modify P
P.lastName = "Sylvie"
P.lastName = "Vartan"
P.age = 52
' display P
Console.Out.Write("Formal parameter after modification: ")
P.identify()
End Sub
End Module
The results obtained are as follows:
Actual parameter before modification: Jean,Dupont,30
Formal parameter before modification: Jean,Dupont,30
Formal parameter after modification: Sylvie,Vartan,52
Actual parameter after modification: Sylvie,Vartan,52
We can see that only one object is created: that of person p1 in the Main procedure, and that the object was indeed modified by the modify function.
3.1.16. An array of people
An object is a piece of data like any other, and as such, multiple objects can be grouped into an array:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test page
Module test
Sub Main()
' an array of people
Dim friends(2) As person
friends(0) = New Person("Jean", "Dupont", 30)
friends(1) = New Person("Sylvie", "Vartan", 52)
friends(2) = New Person("Neil", "Armstrong", 66)
' display
Console.Out.WriteLine("----------------")
Dim i As Integer
For i = 0 To friends.Length - 1
friends(i).identify()
Next i
End Sub
End Module
The statement Dim friends(2) As Person creates an array of 3 elements of type Person. These 3 elements are initialized here with the value Nothing, meaning they do not reference any objects. Again, in a technical sense, we refer to an "array of objects" when it is actually just an array of object references. Creating the object array, which is an object itself, does not create any objects of the type of its elements: this must be done later. The following results are obtained:
3.2. Inheritance by Example
3.2.1. Overview
Here we discuss the concept of inheritance. The purpose of inheritance is to "customize" an existing class so that it meets our needs. Suppose we want to create a Teacher class: a teacher is a specific type of person. They have attributes that other people do not have: the subject they teach, for example. But they also have the attributes of any person: first name, last name, and age. A teacher is therefore a full member of the Person class but has additional attributes. Rather than writing a Teacher class from scratch, we would prefer to build upon the existing Person class and adapt it to the specific characteristics of teachers. This is what the concept of inheritance allows us to do. To express that the Teacher class inherits the properties of the Person class, we write:
Public Class Teacher
Inherits Person
Note the specific two-line syntax. The Person class is called the parent (or base) class, and the Teacher class is called the derived (or child) class. A Teacher object has all the qualities of a Person object: it has the same attributes and methods. These attributes and methods of the parent class are not repeated in the definition of the child class; we simply specify the attributes and methods added by the child class. We assume that the Person class is defined as follows:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' Person class
Public Class Person
' class attributes
Private Shared _nbPeople As Long = 0
' instance attributes
Private _firstName As [String]
Private _lastName As [String]
Private _age As Integer
' constructors
Public Sub New(ByVal FirstName As [String], ByVal LastName As [String], ByVal age As Integer)
' one more person
_nbPeople += 1
' creation
Me._firstName = P
Me._lastName = N
Me._age = age
' followed by
Console.Out.WriteLine("Person constructor(string, string, int)")
End Sub
Public Sub New(ByVal P As Person)
' one more person
_nbPeople += 1
' construction
Me._firstName = P._firstName
Me._lastName = P._lastName
Me._age = P._age
' follow-up
Console.Out.WriteLine("Person constructor(string, string, int)")
End Sub
' class property
Public Shared ReadOnly Property nbPeople() As Long
Get
Return _nbPeople
End Get
End Property
' instance properties
Public Property firstName() As String
Get
Return _firstName
End Get
Set(ByVal Value As String)
_firstName = Value
End Set
End Property
Public Property lastName() As String
Get
Return _surname
End Get
Set(ByVal Value As String)
_name = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' Is the age valid?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("Invalid age (" & Value & ")")
End If
End Set
End Property
Public ReadOnly Property identity() As String
Get
Return "person(" & _firstName & "," & _lastName & "," & age & ")"
End Get
End Property
End Class
The Identify method has been replaced by the read-only Identity property, which identifies the person. We create a Teacher class that inherits from the Person class:
' options
Option Strict On
Option Explicit On
' Namespaces
Imports System
Public Class Teacher
Inherits Person
' attributes
Private _section As Integer
' constructor
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' follow-up
Console.Out.WriteLine("Construction teacher(string, string, int, int)")
End Sub
' section property
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
End Class
The Teacher class adds the following to the methods and attributes of the Person class:
- a `section` attribute, which is the section number to which the teacher belongs within the teaching staff (roughly one section per subject)
- a new constructor that initializes all of a teacher's attributes
The declaration
Public Class Teacher
Inherits Person
indicates that the Teacher class derives from the Person class.
3.2.2. Creating a Teacher object
The constructor for the Teacher class is as follows:
' constructor
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' monitoring
Console.Out.WriteLine("Construction teacher(string, string, int, int)")
End Sub
The declaration
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
declares that the constructor receives four parameters: P, N, age, and section. It must pass three of them (P, N, age) to its base class, in this case the Person class. We know that this class has a constructor Person(string, string, int) that will allow us to create a Person object using the passed parameters (P, N, age). The [Teacher] class passes the parameters (P, N, age) to its base class as follows:
MyBase.New(P, N, age)
Once the base class has been constructed, the construction of the Teacher object continues with the execution of the constructor’s body:
Me._section = section
In summary, the constructor of a derived class:
- passes to its base class the parameters it needs to construct itself
- uses the other parameters to initialize its own attributes
We could have chosen to write:
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
Me._firstName = P
Me._lastName = N
Me._age = age
Me._section = section
' continued
Console.Out.WriteLine("Constructor(string, string, int, int)")
End Sub
That is impossible. The Person class has declared its three fields—_firstName, _lastName, and _age—as private. Only objects of the same class have direct access to these fields. All other objects, including child objects as in this case, must use public methods to access them. This would have been different if the Person class had declared the three fields as protected: it would then have allowed derived classes to have direct access to the three fields. In our example, using the parent class’s constructor was therefore the correct solution and is the standard approach: when constructing a child object, we first call the parent object’s constructor and then complete the initializations specific to the child object (section in our example).
Let’s compile the Person and Teacher classes into assemblies:
dos>vbc /t:library person.vb
Microsoft (R) Visual Basic .NET Compiler version 7.10.3052.4 for Microsoft (R) .NET Framework version 1.1.4322.573
dos>vbc /r:person.dll /t:library teacher.vb
Microsoft® Visual Basic .NET Compiler version 7.10.3052.4 for Microsoft® .NET Framework version 1.1.4322.573
dos>dir
02/25/2004 10:08 1,828 person.vb
02/25/2004 10:11 675 teacher.vb
02/25/2004 10:12 223 test.vb
02/25/2004 10:16 4,096 person.dll
02/25/2004 10:16 3,584 teacher.dll
Note that to compile the teacher child class, we had to reference the person.dll file, which contains the person class. Let’s try a first test program:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New Teacher("Jean", "Dupont", 30, 27).Identity)
End Sub
End Module
This program simply creates a Teacher object (new) and identifies it. The Teacher class does not have an Identity method, but its parent class does have one, which is also public: through inheritance, it becomes a public method of the Teacher class. The results obtained are as follows:
dos>vbc /r:person.dll /r:teacher.dll test.vb
Microsoft (R) Visual Basic .NET Compiler version 7.10.3052.4 for Microsoft (R) .NET Framework version 1.1.4322.573
dos>test
Constructor Person(string, string, int)
Constructor Teacher(string, string, int, int)
person(Jean, Dupont, 30)
We can see that:
- a person object was constructed before the teacher object
- the identity obtained is that of the person object
3.2.3. Overloading a method or property
In the previous example, we had the identity of the person part of the teacher, but some information specific to the Teacher class (the section) is missing. We therefore need to write a property that allows us to identify the teacher:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class Teacher
Inherits Person
' attributes
Private _section As Integer
' constructor
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' followed by
Console.Out.WriteLine("Construction teacher(string,string,int,int)")
End Sub
' section property
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
' Override identity property
Public Shadows ReadOnly Property identity() As String
Get
Return "teacher(" & MyBase.identity & "," & _section & ")"
End Get
End Property
End Class
The identity method of the Teacher class relies on the identity method of its parent class (MyBase.identity) to display its "person" part and then supplements it with the _section field, which is specific to the Teacher class. Note the declaration of the identity property:
Public Shadows ReadOnly Property identity() As String
which indicates that the identity property "hides" the method of the same name that might exist in the parent class. Consider a Teacher object E. This object contains a Person object within it:
![]() |
The identity property is defined in both the Teacher class and its parent Person class. In the child Teacher class, the identity property must be preceded by the shadows keyword to indicate that we are redefining a new identity property for the Teacher class.
Public Shadows ReadOnly Property identity() As String
The Teacher class now has two identity properties:
- the one inherited from the parent Person class
- its own
If E is a Teacher object, E.identity refers to the identity method of the Teacher class. We say that the identity property of the parent class is "overridden" by the identity property of the child class. In general, if O is an object and M is a method, to execute the method O.M, the system searches for a method M in the following order:
- in the class of object O
- in its parent class, if it has one
- in the parent class of its parent class, if it exists
- etc…
Inheritance therefore allows methods/properties with the same name in the parent class to be redefined in the child class. This is what allows the child class to be adapted to its own needs. Combined with polymorphism, which we will discuss shortly, method/property overloading is the main benefit of inheritance. Let’s consider the same example as before:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New Teacher("Jean", "Dupont", 30, 27).Identity)
End Sub
End Module
The results obtained this time are as follows:
Construct Person(string, string, int)
Construct teacher(string, string, int, int)
teacher(person(Jean, Dupont, 30), 27)
3.2.4. Polymorphism
Consider a class hierarchy: C0 C1 C2 … Cn where Ci Cj indicates that class Cj is derived from class Ci. This implies that class Cj has all the characteristics of class Ci plus additional ones. Let Oi be objects of type Ci. It is valid to write:
Indeed, by inheritance, class Cj has all the characteristics of class Ci plus additional ones. Therefore, an object Oj of type Cj contains within it an object of type Ci. The operation
means that Oi is a reference to the object of type Ci contained within the object Oj.
The fact that a variable Oi of class Ci can in fact reference not only an object of class Ci but any object derived from class Ci is called polymorphism: the ability of a variable to reference different types of objects. Let’s take an example and consider the following function independent of any class:
Sub display(ByVal p As Person)
We could just as easily write
as
In the latter case, the formal parameter of type person in the display function will receive a value of type teacher. Since the type teacher derives from the type person, this is valid.
3.2.5. Redefinition and Polymorphism
Let's complete our display procedure:
Sub display(ByVal p As Person)
' displays p's identity
Console.Out.WriteLine(p.identity)
End Sub
The p.identity method returns a string identifying the Person object. What happens in our previous example when dealing with a Teacher object:
Dim e As New Teacher("Lucile", "Dumas", 56, 61)
display(e)
Let's look at the following example:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' a teacher
Dim e As New Teacher("Lucile", "Dumas", 56, 61)
display(e)
' a person
Dim p As New person("Jean", "Dupont", 30)
display(p)
End Sub
' displays
Sub display(ByVal p As person)
' displays p's identity
Console.Out.WriteLine(p.identity)
End Sub
End Module
The results obtained are as follows:
Person constructor(string, string, int)
Constructor teacher(string, string, int, int)
person(Lucile, Dumas, 56)
Construct person(string, string, int)
person(Jean, Dupont, 30)
The execution shows that the statement p.identity executed the identity property of a person each time: first the person contained in the teacher e, then the person p itself. It did not adapt to the object actually passed as a parameter to display. We would have preferred to have the full identity of the teacher e. To achieve this, the notation p.identity would have needed to reference the identity property of the object actually pointed to by p rather than the identity property of the "person" part of the object actually pointed to by p. It is possible to achieve this result by declaring identity as an overridable property in the base class Person:
Public Overridable ReadOnly Property identite() As String
Get
Return "person(" & _firstName & "," & _lastName & "," & age & ")"
End Get
End Property
The overridable keyword makes *identite* a refinable or virtual property. This keyword can also be applied to methods. Child classes that redefine a virtual property or method must use the overrides keyword instead of shadows to qualify their redefined property or method. Thus, in the enseignant class, the identite property is defined as follows:
' Overrides the identity property
Public Overrides ReadOnly Property identity() As String
Get
Return "teacher(" & MyBase.identity & "," & _section & ")"
End Get
End Property
The test program:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' a teacher
Dim e As New Teacher("Lucile", "Dumas", 56, 61)
display(e)
' a person
Dim p As New person("Jean", "Dupont", 30)
display(p)
End Sub
' displays
Sub display(ByVal p As person)
' displays p's identity
Console.Out.WriteLine(p.identity)
End Sub
End Module
then produces the following results:
Construction Person(string, string, int)
Constructor teacher(string, string, int, int)
teacher(person(Lucile, Dumas, 56), 61)
Construct person(string, string, int)
person(Jean, Dupont, 30)
This time, we successfully obtained the teacher's full identity. Let's now redefine a method rather than a property. The Object class is the "parent" class of all VB.NET classes. So when we write:
we are implicitly writing:
The object class defines a virtual ToString method:

The ToString method returns the name of the class to which the object belongs, as shown in the following example:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test2
Sub Main()
' a teacher
Console.Out.WriteLine(New Teacher("Lucile", "Dumas", 56, 61).ToString())
' a person
Console.Out.WriteLine(New Person("Jean", "Dupont", 30).ToString())
End Sub
End Module
The results are as follows:
Person constructor(string, string, int)
Constructor Teacher(string, string, int, int)
teacher
Person construction(string, string, int)
person
Note that although we have not overridden the ToString method in the Person and Teacher classes, we can still see that the ToString method of the Object class is still able to display the actual class name of the object. Let’s override the ToString method in the Person and Teacher classes:
' ToString
Public Overrides Function ToString() As String
' We return the identity property
Return identity
End Function
The definition is the same in both classes. Consider the following test program:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' a teacher
Dim e As New Teacher("Lucile", "Dumas", 56, 61)
display(e)
' a person
Dim p As New person("Jean", "Dupont", 30)
display(p)
End Sub
' displays
Sub display(ByVal p As person)
' displays p's identity
Console.Out.WriteLine(p.identity)
End Sub
End Module
The execution results are as follows:
Construction person(string, string, int)
Constructor teacher(string, string, int, int)
teacher(person(Lucile, Dumas, 56), 61)
Construct person(string, string, int)
person(Jean, Dupont, 30)
3.3. Defining an indexer for a class
Consider the [ArrayList] class predefined in the .NET platform. This class allows you to store objects in a list. It belongs to the [System.Collections] namespace. In the following example, we use this class to store a list of people (in the broad sense):
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports System.Collections
' PeopleList class
Public Class PeopleList
Inherits ArrayList
' to add a person to the list
Public Overloads Sub Add(ByVal p As Person)
MyBase.Add(p)
End Sub
' an indexer
Default Public Shadows Property Item(ByVal i As Integer) As Person
Get
Return CType(MyBase.Item(i), Person)
End Get
Set(ByVal Value As Person)
MyBase.Item(i) = Value
End Set
End Property
' another indexer
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' search for the person named N
Dim i As Integer
For i = 0 To Count - 1
If CType(Me(i), person).name = N Then
Return i
End If
Next i
Return -1
End Get
End Property
' toString
Public Overrides Function ToString() As String
' returns (item1, item2, ..., itemn)
Dim list As String = "("
Dim i As Integer
' iterate through the dynamic array
For i = 0 To (Count - 2)
list += "[" + Me(i).ToString + "]" + ","
Next i 'for
' last element
If Count <> 0 Then
list += "[" + Me(i).ToString + "]"
End If
list += ")"
Return list
End Function
End Class
Let's provide some information about certain properties and methods of the [ArrayList] class:
property that returns the number of elements in the list | |
method for adding an object to the list | |
method that returns the i-th element of the list |
We note that to obtain the i-th element of the list, we did not write [list.Item(i)] but directly [list(i)], which at first glance seems incorrect. This is nevertheless possible because the [ArrayList] class defines a default property [Item] with syntax similar to the following:
Default Public Property Item(ByVal i As Integer) As Object
Get
...
End Get
Set(ByVal Value As Person)
...
End Set
End Property
When the compiler encounters the notation [list(i)], it checks whether the [ArrayList] class has defined a property with the following signature:
Default Public Property Proc(ByVal var As Integer) As Type
Here, it will find the [Item] procedure. It will then translate the notation [list(i)] into [list.Item(i)]. The [Item] property is the default indexed property of the [ArrayList] class. Running the previous program yields the following results:
dos>vbc /r:personne.dll /r:enseignant.dll lstpersonnes1.vb
Microsoft (R) Visual Basic .NET Compiler version 7.10.3052.4 for Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
03/11/2004 18:26 3,584 teacher.dll
03/12/2004 3:34 PM 3,584 lstpersonnes1.exe
03/12/2004 1:39 PM 661 lstpersonnes1.vb
03/11/2004 6:26 PM 4,096 person.dll
dos>lstpersonnes1
Construct person(string, string, int)
Construct person(string, string, int)
Person constructor(string, string, int)
Constructor teacher(string, string, int, int)
person(paul,chenou,31)
person(nicole, chenou, 11)
teacher(person(jacques, sileau, 33), 61)
Let's create a class called [listOfPeople] that would be a list of people, so a specific list that it seems natural to derive from the [ArrayList] class:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports System.Collections
' PeopleList class
Public Class PeopleList
Inherits ArrayList
' to add a person to the list
Public Shadows Sub Add(ByVal p As Object)
' p must be a person
If Not TypeOf(p) Is Person Then
Throw New Exception("The added object (" + p.ToString + ") is not a person")
Else
MyBase.Add(p)
End If
End Sub
' an indexer
Default Public Shadows Property Index(ByVal i As Integer) As Person
Get
Return CType(MyBase.Item(i), Person)
End Get
Set(ByVal Value As Person)
MyBase.Item(i) = Value
End Set
End Property
' toString
Public Overrides Function ToString() As String
' returns (item1, item2, ..., itemn)
Dim list As String = "("
Dim i As Integer
' iterate through the dynamic array
For i = 0 To (Count - 2)
list += "[" + Me(i).ToString + "]" + ","
Next i 'for
' last element
If Count <> 0 Then
list += "[" + Me(i).ToString + "]"
End If
list += ")"
Return list
End Function
End Class
The class has the following methods and properties:
returns a string "representing" the contents of the list | |
method for adding a person to the list | |
default indexed property returning the i-th person in the list |
Let's consider the new features:
A new method [Add] is created.
' to add a person to the list
Public Shadows Sub Add(ByVal p As Object)
' p must be a person
If Not TypeOf(p) Is Person Then
Throw New Exception("The added object (" + p.ToString + ") is not a person")
Else
MyBase.Add(p)
End If
End Sub
There is already one in the parent class [ArrayList] with the same signature, hence the [Shadows] keyword to indicate that the new procedure replaces the one in the parent class. The [Add] method of the child class checks whether the added object is indeed of type [person] or a derived type using the [TypeOf] function. If this is not the case, an exception is thrown using the [Throw] statement. If the added object is indeed of type [person], the [Add] method of the base class is used to perform the addition.
An indexed property is created:
' an indexer
Default Public Shadows Property Index(ByVal i As Integer) As Person
Get
Return CType(MyBase.Item(i), Person)
End Get
Set(ByVal Value As Person)
MyBase.Item(i) = Value
End Set
End Property
There is already a default property named [Item] in the base class with the same signature. Therefore, we must use the [Shadows] keyword to indicate that the new indexed property [Index] will "hide" the base class property [Item]. Note that this is true even if the two properties do not have the same name. The [Index] property allows you to reference the person at index i in the list. It relies on the [Item] property of the base class to access the element at index i of the underlying [ArrayList] object. Type changes are made to account for the fact that the [Item] property works with elements of type [Object], whereas the [Index] property works with elements of type [Person].
Finally, we override the [ToString] method of the [ArrayList] class:
' toString
Public Overrides Function ToString() As String
' returns (el1, el2, ..., eln)
Dim list As String = "("
Dim i As Integer
' iterate through the dynamic array
For i = 0 To (Count - 2)
list += "[" + Me(i).ToString + "]" + ","
Next i 'for
' last element
If Count <> 0 Then
list += "[" + Me(i).ToString + "]"
End If
list += ")"
Return list
End Function
This method returns a string of the form "(e1,e2,...,en)", where e1, e2, ..., en are the elements of the list. Note the notation [Me(i)], which refers to the i-th element of the current object [Me]. This is the default indexed property that is used. Thus, [Me(i)] is equivalent to [Me.Index(i)].
The class code is placed in the file [lstpersonnes2.vb] and compiled:
dos>dir
03/11/2004 18:26 3,584 enseignant.dll
03/12/2004 3:45 PM 970 lstpersonnes2.vb
03/11/2004 6:26 PM 4,096 person.dll
dos>vbc /r:personne.dll /r:enseignant.dll /t:library lstpersonnes2.vb
Microsoft (R) Visual Basic .NET Compiler version 7.10.3052.4 for Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
03/11/2004 6:26 PM 3,584 teacher.dll
03/12/2004 3:50 PM 3,584 lstpersonnes2.dll
03/12/2004 3:45 PM 970 lstpersonnes2.vb
03/11/2004 6:26 PM 4,096 person.dll
A test program is created:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
Imports System.Collections
Module test
Sub Main()
' Create an empty list of people
Dim list As PeopleList = New PeopleList
' Create people
Dim p1 As person = New person("paul", "chenou", 31)
Dim p2 As person = New person("nicole", "chenou", 11)
Dim e1 As teacher = New teacher("jacques", "sileau", 33, 61)
' populating the list
list.Add(p1)
list.Add(p2)
list.Add(e1)
' Displaying the list
Console.Out.WriteLine(list.ToString)
' adding a non-person object
Try
list.Add(4)
Catch e As Exception
Console.Error.WriteLine(e.Message)
End Try
End Sub
End Module
It is compiled:
dos>vbc /r:personne.dll /r:enseignant.dll /r:lstpersonnes2.dll test.vb
Microsoft (R) Visual Basic .NET Compiler version 7.10.3052.4 for Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
03/11/2004 6:26 PM 3,584 teacher.dll
03/12/2004 3:50 PM 3,584 lstpersonnes2.dll
03/12/2004 3:45 PM 970 lstpersonnes2.vb
03/11/2004 6:26 PM 4,096 personne.dll
03/12/2004 3:50 PM 3,584 test.exe
03/12/2004 3:49 PM 623 test.vb
then executed:
dos>test
Construct person(string, string, int)
Construct person(string, string, int)
Construct person(string, string, int)
Construct teacher(string, string, int, int)
([person(paul,chenou,31)],[person(nicole,chenou,11)],[teacher(person(jacques,sileau,33),61)])
The added object (4) is not a person
One might want to write
where l would be of type [listOfPeople]. Here, we want to index the list l not by an element number but by a person's name. To do this, we define a new default indexed property:
' another indexer
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' search for the person named N
Dim i As Integer
For i = 0 To Count - 1
If CType(Me(i), person).name = N Then
Return i
End If
Next i
Return -1
End Get
End Property
The first line
Default Public Shadows ReadOnly Property Index(ByVal N As String) As Integer
indicates that we are creating a new default indexed property. All default properties must have the same name, in this case [Index]. The new [Index] property indexes the PeopleList class using a string N. The result of PeopleList(N) is an integer. This integer will be the position in the list of the person with the name N, or -1 if that person is not in the list. We define only the get property, thereby preventing the assignment listOfPeople("name")=value, which would have required defining the set property. Hence the [ReadOnly] keyword. The [Shadows] keyword is necessary to hide the default property of the base class (even though it does not have the same signature).
In the body of the get method, we iterate through the list of people looking for the name N passed as a parameter. If we find it at position i, we return i; otherwise, we return -1.
A new test program could be as follows:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test page
Test Module
Sub Main()
' a list of people
Dim l As New listOfPeople
' Adding people
l.Add(New person("jean", "dumornet", 10))
l.Add(New person("pauline", "duchemin", 12))
' display
Console.Out.WriteLine(("l=" + l.ToString))
l.Add(New Person("Jacques", "Tartifume", 27))
Console.Out.WriteLine(("l=" + l.ToString))
' change element 1
l(1) = New Person("Sylvie", "Cachan", 5)
' display element 1
Console.Out.WriteLine(("l[1]=" + l(1).ToString))
' display list l
Console.Out.WriteLine(("l=" + l.ToString))
' search for people
Dim names() As String = New [String]() {"cachan", "unknown"}
Dim i As Integer
For i = 0 To names.Length - 1
Dim nameAsInteger As Integer = l(names(i))
If inom <> -1 Then
Console.Out.WriteLine(("person(" & names(i) & ")=" & l(inom).ToString))
Else
Console.Out.WriteLine(("person(" + names(i) + ") does not exist"))
End If
Next i
End Sub
End Module
The execution yields the following results:
person(string, string, int)
Construct person(string, string, int)
l=([person(jean,dumornet,10)],[person(pauline,duchemin,12)])
Construct person(string, string, int)
l=([person(jean,dumornet,10)],[person(pauline,duchemin,12)],[person(jacques,tartifume,27)])
Person construction (string, string, int)
l[1] = person(sylvie, cachan, 5)
l = ([person(jean, dumornet, 10)], [person(sylvie, cachan, 5)], [person(jacques, tartifume, 27)])
person(cachan) = person(sylvie, cachan, 5)
person(unknown) does not exist
3.4. Structures
The VB.NET structure is directly derived from the C language structure and is very similar to a class. A structure is defined as follows:
Structure spersonne
' attributes
...
' properties
...
' constructors
...
' methods
End Structure
Despite similarities in their syntax, there are significant differences between classes and structures. For example, the concept of inheritance does not exist with structures. If we are writing a class that is not intended to be derived from another, what are the differences between structures and classes that will help us choose between the two? Let’s use the following example to find out:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' spersonne structure
Structure spersonne
Public Name As String
Public age As Integer
End Structure
' Person class
Class Person
Public name As String
Public age As Integer
End Class
' A test module
Public Module test
Sub Main()
' a person p1
Dim sp1 As Person
sp1.name = "paul"
sp1.age = 10
Console.Out.WriteLine(("sp1=person(" & sp1.name & "," & sp1.age & ")"))
' a person p2
Dim sp2 As Person = sp1
Console.Out.WriteLine(("sp2=person(" & sp2.name & "," & sp2.age & ")"))
' sp2 is modified
sp2.name = "nicole"
sp2.age = 30
' Checking sp1 and sp2
Console.Out.WriteLine(("sp1=cperson(" & sp1.name & "," & sp1.age & ")"))
Console.Out.WriteLine(("sp2=person(" & sp2.name & "," & sp2.age & ")"))
' a cpersonne cp1
Dim cp1 As New cperson
cp1.name = "paul"
cp1.age = 10
Console.Out.WriteLine(("cP1=cperson(" & cp1.name & "," & cp1.age & ")"))
' a Person P2
Dim cp2 As Person = cp1
Console.Out.WriteLine(("cP2=cperson(" & cp2.name & "," & cp2.age & ")"))
' P2 is modified
cp2.name = "nicole"
cp2.age = 30
' Checking P1 and P2
Console.Out.WriteLine(("cP1=cperson(" & cp1.name & "," & cp1.age & ")"))
Console.Out.WriteLine(("cP2=cperson(" & cp2.name & "," & cp2.age & ")"))
End Sub
End Module
If we run this program, we get the following results:
sp1=spersonne(paul,10)
sp2=spersonne(paul,10)
sp1 = cpersonne(paul, 10)
sp2 = cpersonne(nicole, 30)
cP1 = cperson(paul, 10)
cP2 = cperson(paul, 10)
cP1 = cperson(nicole, 30)
cP2 = cperson(nicole, 30)
Whereas in the previous pages of this chapter we used a Person class, we now use a PersonStructure:
' spersonne structure
Structure spersonne
Public name As String
Public Age As Integer
End Structure
The declaration
Dim sp1 As spersonne
creates a structure (name, age) and the value of sp1 is this structure itself.
The declaration
Dim cp1 As New cpersonne
creates an object [cpersonne] (roughly equivalent to our structure), and cp1 is then the address (the reference) of that object.
To summarize
- in the case of the structure, the value of sp1 is the structure itself
- in the case of a class, the value of p1 is the address of the created object
![]() |
![]() |
When in the program we write
Dim sp2 As spersonne = sp1
a new structure (name, age) is created and initialized with the value of p1, i.e., the structure itself.
![]() |
The structure of sp1 is thus duplicated in sp2. This is a value copy.
The statement
Dim cp2 As cpersonne = cp1
works differently. The value of cp1 is copied into cp2, but since this value is actually the object’s address, the object itself is not duplicated. It simply has two references pointing to it:
![]() |
In the case of the structure, if we modify the value of sp2, we do not modify the value of sp1, as the program demonstrates. In the case of the object, if we modify the object pointed to by cp2, the one pointed to by cp1 is modified since they are the same. This is also demonstrated by the program’s results.
We can therefore conclude from these explanations that:
- the value of a variable of type structure is the structure itself
- the value of a variable of type object is the address of the object it points to
Once this fundamental difference is understood, the structure proves to be very similar to a class, as shown in the following new example:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' Person structure
Person structure
' attributes
Private _name As String
Private _age As Integer
' properties
Public Property name() As String
Get
Return _name
End Get
Set(ByVal Value As String)
_name = value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
_age = value
End Set
End Property
' Constructor
Public Sub New(ByVal NAME As String, ByVal AGE As Integer)
_name = NAME
_age = AGE
End Sub 'New
' TOSTRING
Public Overrides Function ToString() As String
Return "person(" & name & "," & age & ")"
End Function
End Structure
' a test module
Test Module
Sub Main()
' a person p1
Dim p1 As New Person("paul", 10)
Console.Out.WriteLine(("p1=" & p1.ToString))
' a person p2
Dim p2 As Person = p1
Console.Out.WriteLine(("p2=" & p2.ToString))
' p2 is modified
p2.name = "nicole"
p2.age = 30
' Checking p1 and p2
Console.Out.WriteLine(("p1=" & p1.ToString))
Console.Out.WriteLine(("p2=" & p2.ToString))
End Sub
End Module
The following execution results are obtained:
The only notable difference here between a structure and a class is that with a class, the objects p1 and p2 would have had the same value at the end of the program, namely that of p2.
3.5. Interfaces
An interface is a set of method or property prototypes that forms a contract. A class that decides to implement an interface commits to providing an implementation of all the methods defined in the interface. The compiler verifies this implementation. Here, for example, is the definition of the Istats interface:
Public Interface Istats
Function average() As Double
Function standardDeviation() As Double
End Interface
Any class implementing this interface will be declared as
public class C
Implements Istats
...
function average() as Double Implements Istats.average
...
end function
function standardDeviation () as Double Implements Istats. standardDeviation
...
end function
end class
The [mean] and [standardDev] methods must be defined in class C. Consider the following code:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' structure
Public Structure student
Public _name As String
Public _grade As Double
' constructor
Public Sub New(ByVal NAME As String, ByVal GRADE As Double)
Me._name = NAME
Me._score = SCORE
End Sub
End Structure
' notes class
Public Class notes
' attribute
Protected _subject As String
Protected _students() As student
' constructor
Public Sub New(ByVal SUBJECT As String, ByVal STUDENTS() As student)
' storing students and subject
Me._subject = SUBJECT
Me._students = STUDENTS
End Sub
' ToString
Public Overrides Function ToString() As String
Dim value As String = "subject=" + _subject + ", grades=("
Dim i As Integer
' concatenate all grades
For i = 0 To (_students.Length - 1) - 1
value &= "[" & _students(i)._name & "," & _students(i)._grade & "],"
Next i
'last grade
If _students.Length <> 0 Then
value &= "[" & _students(i)._name & "," & _students(i)._grade & "]"
End If
value += ")"
' end
Return value
End Function
End Class
The notes class collects the grades for a class in a subject:
Public Class notes
' attribute
Protected _subject As String
Protected _students() As student
The attributes are declared as protected so they can be accessed from a derived class. The student type is a structure that stores the student's name and their grade in the subject:
Public Structure student
Public _name As String
Public _grade As Double
' constructor
Public Sub New(ByVal NAME As String, ByVal GRADE As Double)
Me._name = NAME
Me._score = SCORE
End Sub
End Structure
We decide to derive this notes class into a notesStats class that would have two additional attributes: the average and the standard deviation of the grades:
Public Class notesStats
Inherits notes
Implements Istats
' attributes
Private _average As Double
Private _standardDeviation As Double
The notesStats class implements the following Istats interface:
Public Interface Istats
Function average() As Double
Function standardDeviation() As Double
End Interface
This means that the notesStats class must have two methods named average and standardDeviation with the signatures specified in the Istats interface. The notesStats class is as follows:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class notesStats
Inherits notes
Implements Istats
' attributes
Private _average As Double
Private _standardDeviation As Double
' constructor
Public Sub New(ByVal SUBJECT As String, ByVal STUDENTS() As Student)
MyBase.New(Subject, Students)
' calculate average of grades
Dim sum As Double = 0
Dim i As Integer
For i = 0 To STUDENTS.Length - 1
sum += STUDENTS(i)._grade
Next i
If STUDENTS.Length <> 0 Then
_average = sum / STUDENTS.Length
Else
_average = -1
End If
' standard deviation
Dim squares As Double = 0
For i = 0 To STUDENTS.Length - 1
squares += Math.Pow(STUDENTS(i)._score - _average, 2)
Next i
If STUDENTS.Length <> 0 Then
_standardDeviation = Math.Sqrt((squares / STUDENTS.Length))
Else
_standardDeviation = -1
End If
End Sub
' ToString
Public Overrides Function ToString() As String
Return MyBase.ToString() & ",average=" & _average & ",standard deviation=" & _standardDev
End Function 'ToString
' Methods of the Istats interface
Public Function average() As Double Implements Istats.average
' returns the average of the scores
Return _average
End Function
Public Function standardDeviation() As Double Implements Istats.standardDeviation
' returns the standard deviation
Return _standardDev
End Function
End Class
The mean _mean and the standard deviation _standardDev are calculated when the object is created. Therefore, the mean and standardDev methods simply return the values of the _mean and _standardDev attributes. Both methods return -1 if the student array is empty.
The following test class:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Test module
Sub Main()
' some students & grades
Dim STUDENTS() As Student = {New Student("Paul", 14), New Student("Nicole", 16), New Student("Jacques", 18)}
' which we store in a notes object
Dim english As New grades("english", STUDENTS)
' and display
Console.Out.WriteLine((English.ToString))
' same with average and standard deviation
English = New notesStats("English", STUDENTS)
Console.Out.WriteLine((English.ToString))
End Sub
End Module
gives the results:
subject=English, grades=([Paul,14],[Nicole,16],[Jacques,18])
subject=English, grades=([Paul,14],[Nicole,16],[Jacques,18]),average=16,standard deviation=1.63299316185545
The notesStats class could very well have implemented the average and standardDev methods on its own without specifying that it implemented the Istats interface. What is the point of interfaces? It is this: a function can accept as a formal parameter a value of type I. Any object of class C that implements interface I can then be an actual parameter of that function. Consider the following example:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' an IExample interface
Public Interface Iexample
Function add(ByVal i As Integer, ByVal j As Integer) As Integer
Function subtract(ByVal i As Integer, ByVal j As Integer) As Integer
End Interface
' a first class
Public Class class1
Implements Iexample
Public Function add(ByVal a As Integer, ByVal b As Integer) As Integer Implements IExample.add
Return a + b + 10
End Function
Public Function subtract(ByVal a As Integer, ByVal b As Integer) As Integer Implements IExample.subtract
Return a - b + 20
End Function
End Class
a second-grade class
Public Class class2
Implements Iexample
Public Function add(ByVal a As Integer, ByVal b As Integer) As Integer Implements IExample.add
Return a + b + 100
End Function
Public Function subtract(ByVal a As Integer, ByVal b As Integer) As Integer Implements IExample.subtract
Return a - b + 200
End Function
End Class
The Iexample interface defines two methods: add and subtract. The classes Class1 and Class2 implement this interface. Note that these classes do nothing else, for the sake of simplifying the example. Now consider the following example:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test class
Test Module
'calculate
Sub calculate(ByVal i As Integer, ByVal j As Integer, ByVal inter As IExample)
Console.Out.WriteLine(inter.add(i, j))
Console.Out.WriteLine(inter.subtract(i, j))
End Sub
' the Main function
Sub Main()
' creation of two objects class1 and class2
Dim c1 As New class1
Dim c2 As New class2
' Calls the static function calculate
calculate(4, 3, c1)
calculate(14, 13, c2)
End Sub
End Module
The calculate function accepts an element of type Iexample as a parameter. It can therefore receive either an object of type class1 or class2 for this parameter. This is what is done in the Main procedure with the following results:
We can see, then, that this property is similar to the polymorphism we discussed for classes. If a set of classes Ci that are not related to one another through inheritance (and thus cannot use inheritance-based polymorphism) share a set of methods with the same signature, it may be useful to group these methods into an interface I from which all the relevant classes would inherit. Instances of these classes Ci can then be used as parameters for functions that accept a parameter of type I, i.e., functions that use only the methods of the Ci objects defined in the interface I and not the specific attributes and methods of the various Ci classes. Finally, note that interface inheritance can be multiple, i.e., we can write
Public Class class
Implements I1, I2, ...
where the Ij are interfaces.
3.6. Namespaces
To write a line on the screen, we use the statement
If we look at the definition of the Console class
Namespace: System
Assembly: Mscorlib (in Mscorlib.dll)
we see that it is part of the System namespace. This means that the Console class should be referred to as System.Console, and we should actually write:
We avoid this by using an imports clause:
We say that we import the System namespace using the imports clause. When the compiler encounters a class name (here, Console), it will look for it in the various namespaces imported by the imports clauses. Here, it will find the Console class in the System namespace. Now let’s note the second piece of information associated with the Console class:
Assembly: Mscorlib (in Mscorlib.dll)
This line indicates in which "assembly" the definition of the Console class is located. When compiling outside of Visual Studio.NET and you need to specify the references to the various DLLs containing the classes you need to use, this information can be useful. Remember that to reference the DLLs required to compile a class, you write:
When creating a class, you can create it within a namespace. The purpose of these namespaces is to avoid name conflicts between classes, for example when they are sold. Consider two companies, E1 and E2, distributing classes packaged respectively in the DLLs E1.dll and E2.dll. Suppose a customer C purchases these two sets of classes, in which both companies have defined a Person class. Customer C compiles a program as follows:
If the source file prog.vb uses the Person class, the compiler will not know whether to use the Person class from E1.dll or the one from E2.dll. It will report an error. If company E1 takes care to create its classes in a namespace called E1 and company E2 in a namespace called E2, the two Person classes will then be named E1.Person and E2.Person. The client must use either E1.Person or E2.Person in its classes, but not Person. The namespace resolves this ambiguity. To create a class in a namespace, you write:
Namespace istia.st
Public Class Person
' class definition
...
end Class
end Namespace
For this example, let's create the Person class we studied earlier in a namespace. We will choose istia.st as the namespace. The Person class becomes:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' Create the istia.st namespace
Namespace istia.st
Public Class Person
' attributes
Private firstName As String
Private lastName As String
Private age As Integer
' method
Public Sub Initialize(ByVal FirstName As String, ByVal LastName As String, ByVal Age As Integer)
Me.firstName = P
Me.lastName = N
Me.age = age
End Sub
' method
Public Sub identify()
Console.Out.WriteLine((firstName & "," & lastName & "," & age))
End Sub
End Class
End Namespace
This class is compiled into person.dll:
dos>vbc /t:library personne.vb
Microsoft (R) Visual Basic .NET Compiler version 7.10.3052.4 for Microsoft (R) .NET Framework version 1.1.4322.573
Now let's use the Person class in a test class:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports istia.st
' test page
Public Module test
Sub Main()
Dim p1 As New Person
p1.initialize("Jean", "Dupont", 30)
p1.identify()
End Sub
End Module
To avoid writing
Dim p1 As New istia.st.person
we have imported the istia.st namespace with an Imports clause:
Imports istia.st
Now let's compile the test program:
dos>dir
03/12/2004 18:06 3 584 personne.dll
03/11/2004 6:27 PM 610 personne.vb
03/12/2004 6:05 PM 254 test.vb
dos>vbc /r:personne.dll test.vb
Microsoft (R) Visual Basic .NET Compiler version 7.10.3052.4 for Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
03/12/2004 6:06 PM 3,584 person.dll
03/11/2004 6:27 PM 610 personne.vb
03/12/2004 6:08 PM 3,072 test.exe
03/12/2004 6:05 PM 254 test.vb
This produces a test.exe file which, when executed, yields the following results:
3.7. The tax calculation Example
We revisit the tax calculation already covered in the previous chapter and process it using a class. Let’s review the problem:
We consider the simplified case of a taxpayer who has only his or her salary to report:
- we calculate the number of tax brackets for the employee as nbParts = nbEnfants / 2 + 1 if they are unmarried, and nbEnfants / 2 + 2 if they are married, where nbEnfants is the number of children.
- If they have at least three children, they receive an additional half-share
- We calculate their taxable income R = 0.72 * S, where S is their annual salary
- We calculate their family coefficient QF = R / nbParts
- we calculate his tax I. Consider the following table:
12620.0 | 0 | 0 |
13,190 | 0.05 | 631 |
15,640 | 0.1 | 1,290.5 |
24,740 | 0.15 | 2,072.5 |
31,810 | 0.2 | 3,309.5 |
39,970 | 0.25 | 4,900 |
48,360 | 0.3 | 6,898.5 |
55,790 | 0.35 | 9,316.5 |
92,970 | 0.4 | 12,106 |
127,860 | 0.45 | 16,754.5 |
151,250 | 0.50 | 23,147.5 |
172,040 | 0.55 | 30,710 |
195,000 | 0.60 | 39,312 |
0 | 0.65 | 49,062 |
Each row has 3 fields. To calculate tax I, find the first row where QF <= field1. For example, if QF = 23,000, the row found will be
Tax I is then equal to 0.15*R - 2072.5*nbParts. If QF is such that the condition QF<=field1 is never met, then the coefficients from the last row are used. Here:
which gives the tax I = 0.65 * R - 49062 * nbParts.
The **impot** class will be defined as follows:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class Tax
' the data needed to calculate the tax
' comes from an external source
Private limits(), coeffR(), coeffN() As Decimal
' constructor
Public Sub New(ByVal LIMITS() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' check that the 3 arrays are the same size
Dim OK As Boolean = LIMITS.Length = COEFFR.Length And LIMITS.Length = COEFFN.Length
If Not OK Then
Throw New Exception("The three arrays provided do not have the same size(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
End If
' All good
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' tax calculation
Public Function calculate(ByVal married As Boolean, ByVal numChildren As Integer, ByVal salary As Long) As Long
' calculate the number of shares
Dim nbShares As Decimal
If married Then
nbParts = CDec(nbChildren) / 2 + 2
Else
nbParts = CDec(nbChildren) / 2 + 1
End If
If nbChildren >= 3 Then
nbParts += 0.5D
End If
' Calculate taxable income & Family Quotient
Dim income As Decimal = 0.72D * salary
Dim QF As Decimal = income / numParts
' tax calculation
limits((limits.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limits(i)
i += 1
End While
Return CLng(revenue * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
A tax object is created with the data needed to calculate a taxpayer's tax. This is the stable part of the object. Once this object is created, its **calculate** method can be called repeatedly to calculate the taxpayer's tax based on their marital status (married or not), number of children, and annual salary. A test program might look like this:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports Microsoft.VisualBasic
Test Module
Sub Main()
' Interactive tax calculation program
' The user enters three pieces of data via the keyboard: married, number of children, salary
' the program then displays the tax due
Const syntax As String = "syntax: married noChildren salary" + ControlChars.Lf + "married: o for married, n for unmarried" + ControlChars.Lf + "noChildren: number of children" + ControlChars.Lf + "salary: annual salary in F"
' data tables required to calculate the tax
Dim limits() As Decimal = {12620D, 13190D, 15640D, 24740D, 31810D, 39970D, 48360D, 55790D, 92970D, 127860D, 151250D, 172040D, 195000D, 0D}
Dim coeffR() As Decimal = {0D, 0.05D, 0.1D, 0.15D, 0.2D, 0.25D, 0.3D, 0.35D, 0.4D, 0.45D, 0.5D, 0.55D, 0.6D, 0.65D}
Dim coeffN() As Decimal = {0D, 631D, 1290.5D, 2072.5D, 3309.5D, 4900D, 6898.5D, 9316.5D, 12106D, 16754.5D, 23147.5D, 30710D, 39312D, 49062D}
' Create a tax object
Dim objTax As Tax = Nothing
Try
taxObj = New tax(limits, coeffR, coeffN)
Catch ex As Exception
Console.Error.WriteLine(("The following error occurred: " + ex.Message))
Environment.Exit(1)
End Try
' infinite loop
Dim husband As String
Dim numChildren As Integer
Dim salary As Long
While True
' Request the parameters for the tax calculation
Console.Out.Write("Tax calculation parameters in married with n children, salary, or nothing format. Press Enter to continue:")
Dim parameters As String = Console.In.ReadLine().Trim()
' anything to do?
If parameters Is Nothing OrElse parameters = "" Then
Exit While
End If
' Check the number of arguments in the entered line
Dim error As Boolean = False
Dim args As String() = parameters.Split(Nothing)
Dim numParameters As Integer = args.Length
If nbParameters <> 3 Then
Console.Error.WriteLine(syntax)
error = True
End If
' Checking parameter validity
If Not error Then
' married
married = args(0).ToLower()
If married <> "y" And married <> "n" Then
error = True
End If
' nbChildren
Try
nbChildren = Integer.Parse(args(1))
If nbChildren < 0 Then
Throw New Exception
End If
Catch
error = True
End Try
' salary
Try
salary = Integer.Parse(args(2))
If salary < 0 Then
Throw New Exception
End If
Catch
error = True
End Try
End If
' if the parameters are correct, calculate the tax
If Not error Then
Console.Out.WriteLine(("tax=" & objTax.calculate(married = "o", numChildren, salary) & " F"))
Else
Console.Error.WriteLine(syntax)
End If
End While
End Sub
End Module
Here is an example of the previous program in action:
dos>vbc /t:library impots.vb
Microsoft (R) Visual Basic .NET Compiler version 7.10.3052.4 for Microsoft (R) .NET Framework version 1.1.4322.573
dir>dir
03/12/2004 6:24 PM 4,096 impots.dll
03/12/2004 6:20 PM 1,483 impots.vb
03/12/2004 6:21 PM 2,805 test.vb
dos>vbc /r:impots.dll test.vb
Microsoft (R) Visual Basic .NET Compiler version 7.10.3052.4 for Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
03/12/2004 6:24 PM 4,096 impots.dll
03/12/2004 6:20 PM 1,483 impots.vb
03/12/2004 6:26 PM 6,144 test.exe
03/12/2004 6:21 PM 2,805 test.vb
dos>test
Tax calculation parameters in the format married nbChildren salary or nothing to stop :x x x
syntax: married nbChildren salary
married: o for married, n for unmarried
nbChildren: number of children
salary: annual salary in F
Tax calculation parameters in the "married with n children, salary" format or nothing to stop: o 2 200000
tax=22,504 F
Tax calculation parameters for married status, number of children, and salary or nothing to stop:





