Skip to content

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

    dim i, j as integer

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:

private
A private field is accessible only by the class’s internal methods
public
A public field is accessible by any function, whether or not it is defined within the class
protected
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:

dim p1 as Person p1
p1.firstName="Jean"
p1.lastName="Dupont"
p1.age=30

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:

dim p1 as person
p1.initialize("Jean", "Dupont", 30)

The syntax p1.initialize is valid because initialize is public.

3.1.4. The new operator

The sequence of statements

dim p1 as person
p1.initialize("Jean", "Dupont", 30)

is incorrect. The statement

dim p1 as person

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:

dim p1 as person = nothing

where we explicitly indicate with the keyword nothing that the variable p1 does not yet reference any object. When we then write

p1.initialize("Jean", "Dupont", 30)

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:

dim p1 as person = new person()

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

p1.initialize("Jean", "Dupont", 30)

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:

p1.initialize("Jean", "Dupont", 30)

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
dos>person1
Jean,Dupont,30

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

Module test1
    Sub Main()
        Dim p1 As New Person
        p1.initialize("Jean", "Dupont", 30)
        p1.identify()
    End Sub
End Module
person2.vb

' options
Option Explicit On
Option Strict On

' namespaces
Imports System

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    'initialize

    ' method
    Public Sub identify()
        Console.Out.WriteLine((firstName & "," & lastName & "," & age))
    End Sub    'identifies
End Class 'person

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:

vbc /r:fic1.dll /r:fic2.dll ... sourceFile.vb

Running the program test1.exe produces the following results:

dos>test
Jean,Dupont,30

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:

p1=Jean,Dupont,30
p2=John,Dupont,30

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:

    dim p1 as person
    p1 = new Person

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:

p1=Jean,Dupont,30
p2=Jean,Dupont,30

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

dim p1 as person = new person("Jean", "Dupont", 30)

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:

    p1 = nothing

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:

dim p2 as person=p1

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:

dim p3 as person = new person(p1);

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:

dos>test
Person constructor(String, String, Integer)
Person constructor(person)
Jean, Dupont, 30

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:

P=(Jean,Michelin,34)
P=(Jean,Michelin,56)

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

        Console.Out.WriteLine(("P=(" & P.firstName & "," & P.lastName & "," & P.age & ")"))

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

        P.age = 56

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:

P=(Jean,Michelin,34)
P=(Jean,Michelin,56)
age (-4) invalid

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:

    Private Shared _nbPeople As Long = 0

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:

    Public Shared ReadOnly Property nbPeople() As Long
        Get
            Return _nbPeople
        End Get
    End Property

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:

    Number of people created: 2

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:

Jean,Dupont,30
Sylvie,Vartan,52
Neil, Armstrong, 66

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:

    Oi = Oj with j > i

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

Oi = Oj

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

    dim p as person
    ...
    display(p);

as

    dim e as teacher
    ...
    display(e);

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:

    public class Person

we are implicitly writing:

    public class Person 
         inherits object

The object class defines a virtual ToString method:

Image

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:

Count
property that returns the number of elements in the list
Add(Object)
method for adding an object to the list
Item(Integer i)
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:

ToString
returns a string "representing" the contents of the list
Add(person)
method for adding a person to the list
Item(Integer i)
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

dim p as person=l("name")

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:

p1=person(paul,10)
p2=person(paul,10)
p1 = person(paul, 10)
p2=person(nicole,30)

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:

17
21
127
201

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

Console.Out.WriteLine(...)

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:

System.Console.Out.WriteLine(...)

We avoid this by using an imports clause:

imports System
...
Console.Out.WriteLine(...)

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:

vbc /r:fic1.dll /r:fic2.dll ... prog.vb

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:

vbc /r:E1.dll /r:E2.dll prog.vb

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>dir
03/11/2004  6:27 PM               610 person.vb
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
dos>dir
03/12/2004  6:06 PM             3,584 person.dll
03/11/2004  6:27 PM               610 person.vb

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:

Jean,Dupont,30

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

    24740        0.15        2072.5

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:

    0                0.65        49,062

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:

dir>dir
03/12/2004  6:20 PM             1,483 impots.vb
03/12/2004  6:21 PM             2 805 test.vb
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: