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 personne
    ' attributes
    Private prenom As String
    Private nom As String
    Private age As Integer
 
    ' method
    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub
 
    ' method
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & 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 classe
    private  donnée ou méthode ou propriété privée
    public  donnée ou méthode ou propriété publique
    protected  donnée ou méthode ou propriété protégée
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 personne
    ' attributes
    Private prenom As String
    Private nom As String
    Private age As Integer
 
    ' method
    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub
 
    ' method
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & 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 personne p1
p1.prenom="Jean"
p1.nom="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 personne
p1.initialise("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 personne
p1.initialise("Jean","Dupont",30)

is incorrect. The statement

dim p1 as personne

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 personne=nothing

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

p1.initialise("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 personne=new personne()

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.initialise("Jean","Dupont",30)

is valid.

3.1.5. The keyword Me

Let's look at the code for the *Initialize* method:


    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        Me.prenom = P
        Me.nom = 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.initialise("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 initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        prenom = P
        nom = 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 personne
    ' attributes
    Private prenom As String
    Private nom As String
    Private age As Integer
 
    ' method
    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub
 
    ' method
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & age))
    End Sub
End Class

and the results obtained:

dos>vbc personne1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>personne1
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    'identify
End Class 'person
 

The Person class is compiled by the following statement:

dos>vbc /t:library personne2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573

dos>dir
24/02/2004  16:50                  509 personne2.vb
24/02/2004  16:49                  143 test.vb
24/02/2004  16:50                3 584 personne2.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
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
24/02/2004  16:50                  509 personne2.vb
24/02/2004  16:49                  143 test.vb
24/02/2004  16:50                3 584 personne2.dll
24/02/2004  16:51                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 ... fichierSource.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 initialise(ByVal P As personne)
        prenom = P.prenom
        nom = P.nom
        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 personne
        p1.initialise("Jean", "Dupont", 30)
        Console.Out.Write("p1=")
        p1.identifie()
        Dim p2 As New personne
        p2.initialise(p1)
        Console.Out.Write("p2=")
        p2.identifie()
    End Sub
End Module

and its results:

p1=Jean,Dupont,30
p2=Jean,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 objet as classe =new classe(arg1,arg2, ... argn)

or


        dim objet as classe

        objet=new classe(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 personne
    p1=new personne

Let’s create two constructors for our Person class:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
 
' the person class
Public Class personne
    ' attributes
    Private prenom As String
    Private nom As String
    Private age As Integer
 
    ' manufacturers
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        initialise(P, N, age)
    End Sub
 
    Public Sub New(ByVal P As personne)
        initialise(P)
    End Sub
 
    ' object initialization methods
    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub
 
    Public Sub initialise(ByVal P As personne)
        prenom = P.prenom
        nom = P.nom
        Me.age = P.age
    End Sub
 
    ' method
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & 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 pg
Module test
    Sub Main()
        Dim p1 As New personne("Jean", "Dupont", 30)
        Console.Out.Write("p1=")
        p1.identifie()
        Dim p2 As New personne(p1)
        Console.Out.Write("p2=")
        p2.identifie()
    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 pg
Module test
    Sub Main()
        ' p1
        Dim p1 As New personne("Jean", "Dupont", 30)
        Console.Out.Write("p1=")
        p1.identifie()
 
        ' p2 references the same object as p1
        Dim p2 As personne = p1
        Console.Out.Write("p2=")
        p2.identifie()
 
        ' p3 references an object that will be a copy of the object referenced by p1
        Dim p3 As New personne(p1)
        Console.Out.Write("p3=")
        p3.identifie()
 
        ' change the state of the object referenced by p1
        p1.initialise("Micheline", "Benoît", 67)
        Console.Out.Write("p1=")
        p1.identifie()
 
        ' as p2=p1, the object referenced by p2 must have changed state
        Console.Out.Write("p2=")
        p2.identifie()
 
        ' as p3 does not reference the same object as p1, the object referenced by p3 must not have changed
        Console.Out.Write("p3=")
        p3.identifie()
    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 personne=new personne("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 personne=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 personne =new personne(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 pg
Module test
    Sub Main()
        Dim p As New personne(New personne("Jean", "Dupont", 30))
        p.identifie()
    End Sub
End Module

and let's modify the constructors of the Person class so that they display a message:


    ' manufacturers
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        Console.Out.WriteLine("Constructeur personne(String, String, integer)")
        initialise(P, N, age)
    End Sub
 
    Public Sub New(ByVal P As personne)
        Console.Out.WriteLine("Constructeur personne(personne)")
        initialise(P)
    End Sub

We get the following results:

dos>test
Constructeur personne(String, String, integer)
Constructeur personne(personne)
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 personne
 
    ' attributes
    Private prenom As [String]
    Private nom As [String]
    Private age As Integer

    ' manufacturers
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub
 
    Public Sub New(ByVal P As personne)
        Me.prenom = P.prenom
        Me.nom = P.nom
        Me.age = P.age
    End Sub
 
    ' identifies
    Public Sub identifie()
        Console.Out.WriteLine((prenom + "," + nom + "," + age))
    End Sub
 
    ' accessors
    Public Function getPrenom() As [String]
        Return prenom
    End Function
 
    Public Function getNom() As [String]
        Return nom
    End Function
 
    Public Function getAge() As Integer
        Return age
    End Function
 
    'modifiers
    Public Sub setPrenom(ByVal P As [String])
        Me.prenom = P
    End Sub
 
    Public Sub setNom(ByVal N As [String])
        Me.nom = 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 pg
Public Module test
    Sub Main()
        Dim P As New personne("Jean", "Michelin", 34)
        Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
        P.setAge(56)
        Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & 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
 
' class person
Public Class personne
 
    ' attributes
    Private _prenom As [String]
    Private _nom As [String]
    Private _age As Integer
 
 
    ' manufacturers
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        Me._prenom = P
        Me._nom = N
        Me._age = age
    End Sub
 
 
    Public Sub New(ByVal P As personne)
        Me._prenom = P._prenom
        Me._nom = P._nom
        Me._age = P._age
    End Sub
 
    ' identifies
    Public Sub identifie()
        Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
    End Sub
 
    ' properties
    Public Property prenom() As String
        Get
            Return _prenom
        End Get
        Set(ByVal Value As String)
            _prenom = Value
        End Set
    End Property
 
    Public Property nom() As String
        Get
            Return _nom
        End Get
        Set(ByVal Value As String)
            _nom = Value
        End Set
    End Property
 
    Public Property age() As Integer
        Get
            Return _age
        End Get
        Set(ByVal Value As Integer)
            ' valid age?
            If Value >= 0 Then
                _age = Value
            Else
                Throw New Exception("âge (" & Value & ") invalide")
            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 nom() 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 pg
Module test
    Sub Main()
        Dim P As New personne("Jean", "Michelin", 34)
        Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
        P.age = 56
        Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & 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.prenom & "," & P.nom & "," & 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)
âge (-4) invalide

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 _nbPersonnes 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 nbPersonnes() As Long
        Get
            Return _nbPersonnes
        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 personne
    ' class attributes
    Private Shared _nbPersonnes As Long = 0
 
    ' instance attributes
    Private _prenom As [String]
    Private _nom As [String]
    Private _age As Integer
 
    ' manufacturers
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        ' one more person
        _nbPersonnes += 1
        Me._prenom = P
        Me._nom = N
        Me._age = age
    End Sub
 
    Public Sub New(ByVal P As personne)
        ' one more person
        _nbPersonnes += 1
        Me._prenom = P._prenom
        Me._nom = P._nom
        Me._age = P._age
    End Sub
 
    ' identifies
    Public Sub identifie()
        Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
    End Sub
 
    ' class property
    Public Shared ReadOnly Property nbPersonnes() As Long
        Get
            Return _nbPersonnes
        End Get
    End Property
 
    ' instance properties
    Public Property prenom() As String
        Get
            Return _prenom
        End Get
        Set(ByVal Value As String)
            _prenom = Value
        End Set
    End Property
 
    Public Property nom() As String
        Get
            Return _nom
        End Get
        Set(ByVal Value As String)
            _nom = Value
        End Set
    End Property
 
    Public Property age() As Integer
        Get
            Return _age
        End Get
        Set(ByVal Value As Integer)
            ' valid age?
            If Value >= 0 Then
                _age = Value
            Else
                Throw New Exception("âge (" & Value & ") invalide")
            End If
        End Set
    End Property
End Class

With the following program:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
 
' test pg
Module test
    Sub Main()
        Dim p1 As New personne("Jean", "Dupont", 30)
        Dim p2 As New personne(p1)
        Console.Out.WriteLine(("Nombre de personnes créées : " & personne.nbPersonnes))
    End Sub
End Module

The following results are obtained:

    Nombre de personnes créées : 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 pg
Module test
    Sub Main()
        ' one person p1
        Dim p1 As New personne("Jean", "Dupont", 30)
 
        ' p1 display
        Console.Out.Write("Paramètre effectif avant modification : ")
        p1.identifie()
 
        ' modification p1
        modifie(p1)
 
        ' p1 display
        Console.Out.Write("Paramètre effectif après modification : ")
        p1.identifie()
    End Sub

    Sub modifie(ByVal P As personne)
        ' display person P
        Console.Out.Write("Paramètre formel avant modification : ")
        P.identifie()
        ' modification P
        P.prenom = "Sylvie"
        P.nom = "Vartan"
        P.age = 52
        ' display P
        Console.Out.Write("Paramètre formel après modification : ")
        P.identifie()
    End Sub
End Module

The results obtained are as follows:

Paramètre effectif avant modification : Jean,Dupont,30
Paramètre formel avant modification : Jean,Dupont,30
Paramètre formel après modification : Sylvie,Vartan,52
Paramètre effectif après 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 pg
Module test
    Sub Main()
        ' a table of people
        Dim amis(2) As personne
        amis(0) = New personne("Jean", "Dupont", 30)
        amis(1) = New personne("Sylvie", "Vartan", 52)
        amis(2) = New personne("Neil", "Armstrong", 66)
        ' display
        Console.Out.WriteLine("----------------")
        Dim i As Integer
        For i = 0 To amis.Length - 1
            amis(i).identifie()
        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 enseignant
    Inherits personne

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
 
' class person
Public Class personne
 
    ' class attributes
    Private Shared _nbPersonnes As Long = 0
 
    ' instance attributes
    Private _prenom As [String]
    Private _nom As [String]
    Private _age As Integer
 
 
    ' manufacturers
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        ' one more person
        _nbPersonnes += 1
        ' construction
        Me._prenom = P
        Me._nom = N
        Me._age = age
        ' follow-up
        Console.Out.WriteLine("Construction personne(string, string, int)")
    End Sub
 
 
    Public Sub New(ByVal P As personne)
        ' one more person
        _nbPersonnes += 1
        ' construction
        Me._prenom = P._prenom
        Me._nom = P._nom
        Me._age = P._age
        ' follow-up
        Console.Out.WriteLine("Construction personne(string, string, int)")
    End Sub
 
    ' class property
    Public Shared ReadOnly Property nbPersonnes() As Long
        Get
            Return _nbPersonnes
        End Get
    End Property
 
    ' instance properties
    Public Property prenom() As String
        Get
            Return _prenom
        End Get
        Set(ByVal Value As String)
            _prenom = Value
        End Set
    End Property
 
    Public Property nom() As String
        Get
            Return _nom
        End Get
        Set(ByVal Value As String)
            _nom = Value
        End Set
    End Property
 
    Public Property age() As Integer
        Get
            Return _age
        End Get
        Set(ByVal Value As Integer)
            ' valid age?
            If Value >= 0 Then
                _age = Value
            Else
                Throw New Exception("âge (" & Value & ") invalide")
            End If
        End Set
    End Property
 
    Public ReadOnly Property identite() As String
        Get
            Return "personne(" & _prenom & "," & _nom & "," & 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 enseignant
    Inherits personne
    ' attributes
    Private _section As Integer
 
    ' manufacturer
    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 enseignant(string,string,int,int)")
    End Sub
 
    ' property section
    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 enseignant
    Inherits personne

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:


    ' manufacturer
    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 enseignant(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._prenom = P
        Me._nom = N
        Me._age = age
        Me._section = section
        ' follow-up
        Console.Out.WriteLine("Construction enseignant(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 personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573

dos>vbc /r:personne.dll /t:library enseignant.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573

dos>dir
25/02/2004  10:08                1 828 personne.vb
25/02/2004  10:11                  675 enseignant.vb
25/02/2004  10:12                  223 test.vb
25/02/2004  10:16                4 096 personne.dll
25/02/2004  10:16                3 584 enseignant.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 enseignant("Jean", "Dupont", 30, 27).identite)
    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:personne.dll /r:enseignant.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573

dos>test
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(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 enseignant
    Inherits personne
    ' attributes
    Private _section As Integer
 
    ' manufacturer
    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 enseignant(string,string,int,int)")
    End Sub
 
    ' property section
    Public Property section() As Integer
        Get
            Return _section
        End Get
        Set(ByVal Value As Integer)
            _section = Value
        End Set
    End Property
 
    ' overload property identity
    Public Shadows ReadOnly Property identite() As String
        Get
            Return "enseignant(" & MyBase.identite & "," & _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 identite() 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 identite() 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 enseignant("Jean", "Dupont", 30, 27).identite)
    End Sub
End Module

The results obtained this time are as follows:

Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(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 avec 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 affiche(ByVal p As personne)

We could just as easily write

    dim p as personne
    ...
    affiche(p);

as

    dim e as enseignant
    ...
    affiche(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 affiche(ByVal p As personne)
        ' displays identity of p
        Console.Out.WriteLine(p.identite)
    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 enseignant("Lucile", "Dumas", 56, 61)
        affiche(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 enseignant("Lucile", "Dumas", 56, 61)
        affiche(e)
        ' a person
        Dim p As New personne("Jean", "Dupont", 30)
        affiche(p)
    End Sub
 
    ' poster
    Sub affiche(ByVal p As personne)
        ' displays identity of p
        Console.Out.WriteLine(p.identite)
    End Sub
End Module

The results obtained are as follows:

Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Lucile,Dumas,56)
Construction personne(string, string, int)
personne(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 "personne(" & _prenom & "," & _nom & "," & 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:


    ' overload property identity
    Public Overrides ReadOnly Property identite() As String
        Get
            Return "enseignant(" & MyBase.identite & "," & _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 enseignant("Lucile", "Dumas", 56, 61)
        affiche(e)
        ' a person
        Dim p As New personne("Jean", "Dupont", 30)
        affiche(p)
    End Sub
 
    ' poster
    Sub affiche(ByVal p As personne)
        ' displays identity of p
        Console.Out.WriteLine(p.identite)
    End Sub
End Module

then produces the following results:

Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Lucile,Dumas,56),61)
Construction personne(string, string, int)
personne(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 personne

we are implicitly writing:

    public class personne 
         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 enseignant("Lucile", "Dumas", 56, 61).ToString())
        ' a person
        Console.Out.WriteLine(New personne("Jean", "Dupont", 30).ToString())
    End Sub
End Module

The results are as follows:

Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant
Construction personne(string, string, int)
personne

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 identite
    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 enseignant("Lucile", "Dumas", 56, 61)
        affiche(e)
        ' a person
        Dim p As New personne("Jean", "Dupont", 30)
        affiche(p)
    End Sub
 
    ' poster
    Sub affiche(ByVal p As personne)
        ' displays identity of p
        Console.Out.WriteLine(p.identite)
    End Sub
End Module

The execution results are as follows:

Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Lucile,Dumas,56),61)
Construction personne(string, string, int)
personne(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
 
' class listeDePersonnes
Public Class listeDePersonnes
    Inherits ArrayList
 
    ' to add a person to the list
    Public Overloads Sub Add(ByVal p As personne)
        MyBase.Add(p)
    End Sub
 
    ' an indexer
    Default Public Shadows Property Item(ByVal i As Integer) As personne
        Get
            Return CType(MyBase.Item(i), personne)
        End Get
        Set(ByVal Value As personne)
            MyBase.Item(i) = Value
        End Set
    End Property
 
    ' another indexer
    Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
        Get
            ' we search for the person with name N
            Dim i As Integer
            For i = 0 To Count - 1
                If CType(Me(i), personne).nom = N Then
                    Return i
                End If
            Next i
            Return -1
        End Get
    End Property
 
    ' toString
    Public Overrides Function ToString() As String
        ' render (él1, él2, ..., éln)
        Dim liste As String = "("
        Dim i As Integer
        ' browse the dynamic table
        For i = 0 To (Count - 2)
            liste += "[" + Me(i).ToString + "]" + ","
        Next i        'for
        ' last element
        If Count <> 0 Then
            liste += "[" + Me(i).ToString + "]"
        End If
        liste += ")"
        Return liste
    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 personne)
...
        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
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004  18:26             3 584 enseignant.dll
12/03/2004  15:34             3 584 lstpersonnes1.exe
12/03/2004  13:39               661 lstpersonnes1.vb
11/03/2004  18:26             4 096 personne.dll
dos>lstpersonnes1
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(paul,chenou,31)
personne(nicole,chenou,11)
enseignant(personne(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
 
' class listeDePersonnes
Public Class listeDePersonnes
    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 personne Then
            Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
        Else
            MyBase.Add(p)
        End If
    End Sub
 
    ' an indexer
    Default Public Shadows Property Index(ByVal i As Integer) As personne
        Get
            Return CType(MyBase.Item(i), personne)
        End Get
        Set(ByVal Value As personne)
            MyBase.Item(i) = Value
        End Set
    End Property
 
    ' toString
    Public Overrides Function ToString() As String
        ' render (él1, él2, ..., éln)
        Dim liste As String = "("
        Dim i As Integer
        ' browse the dynamic table
        For i = 0 To (Count - 2)
            liste += "[" + Me(i).ToString + "]" + ","
        Next i        'for
        ' last element
        If Count <> 0 Then
            liste += "[" + Me(i).ToString + "]"
        End If
        liste += ")"
        Return liste
    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 personne Then
            Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
        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 personne
        Get
            Return CType(MyBase.Item(i), personne)
        End Get
        Set(ByVal Value As personne)
            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
        ' render (él1, él2, ..., éln)
        Dim liste As String = "("
        Dim i As Integer
        ' browse the dynamic table
        For i = 0 To (Count - 2)
            liste += "[" + Me(i).ToString + "]" + ","
        Next i        'for
        ' last element
        If Count <> 0 Then
            liste += "[" + Me(i).ToString + "]"
        End If
        liste += ")"
        Return liste
    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
11/03/2004  18:26             3 584 enseignant.dll
12/03/2004  15:45               970 lstpersonnes2.vb
11/03/2004  18:26             4 096 personne.dll
dos>vbc /r:personne.dll /r:enseignant.dll /t:library lstpersonnes2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004  18:26             3 584 enseignant.dll
12/03/2004  15:50             3 584 lstpersonnes2.dll
12/03/2004  15:45               970 lstpersonnes2.vb
11/03/2004  18:26             4 096 personne.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 liste As listeDePersonnes = New listeDePersonnes
        ' creating people
        Dim p1 As personne = New personne("paul", "chenou", 31)
        Dim p2 As personne = New personne("nicole", "chenou", 11)
        Dim e1 As enseignant = New enseignant("jacques", "sileau", 33, 61)
        ' list filling
        liste.Add(p1)
        liste.Add(p2)
        liste.Add(e1)
        ' list display
        Console.Out.WriteLine(liste.ToString)
        ' add an object other than person
        Try
            liste.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
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004  18:26             3 584 enseignant.dll
12/03/2004  15:50             3 584 lstpersonnes2.dll
12/03/2004  15:45               970 lstpersonnes2.vb
11/03/2004  18:26             4 096 personne.dll
12/03/2004  15:50             3 584 test.exe
12/03/2004  15:49               623 test.vb

then executed:

dos>test
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
([personne(paul,chenou,31)],[personne(nicole,chenou,11)],[enseignant(personne(jacques,sileau,33),61)])
L'objet ajouté (4) n'est pas une personne

One might want to write

dim p as personne=l("nom")

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
            ' we search for the person with name N
            Dim i As Integer
            For i = 0 To Count - 1
                If CType(Me(i), personne).nom = 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 pg
Module test
    Sub Main()
        ' a list of people
        Dim l As New listeDePersonnes
        ' add people
        l.Add(New personne("jean", "dumornet", 10))
        l.Add(New personne("pauline", "duchemin", 12))
        ' display
        Console.Out.WriteLine(("l=" + l.ToString))
        l.Add(New personne("jacques", "tartifume", 27))
        Console.Out.WriteLine(("l=" + l.ToString))
        ' change item 1
        l(1) = New personne("sylvie", "cachan", 5)
        ' display element 1
        Console.Out.WriteLine(("l[1]=" + l(1).ToString))
        ' display list l
        Console.Out.WriteLine(("l=" + l.ToString))
        ' people search
        Dim noms() As String = New [String]() {"cachan", "inconnu"}
        Dim i As Integer
        For i = 0 To noms.Length - 1
            Dim inom As Integer = l(noms(i))
            If inom <> -1 Then
                Console.Out.WriteLine(("personne(" & noms(i) & ")=" & l(inom).ToString))
            Else
                Console.Out.WriteLine(("personne(" + noms(i) + ") n'existe pas"))
            End If
        Next i
    End Sub    
End Module

The execution yields the following results:

personne(string, string, int)
Construction personne(string, string, int)
l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)])
Construction personne(string, string, int)
l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)],[personne(jacques,tartifume,27)])
Construction personne(string, string, int)
l[1]=personne(sylvie,cachan,5)
l=([personne(jean,dumornet,10)],[personne(sylvie,cachan,5)],[personne(jacques,tartifume,27)])
personne(cachan)=personne(sylvie,cachan,5)
personne(inconnu) n'existe pas

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
' attributs
    ...
' propriétés
...
' constructeurs
...
' méthodes
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 nom As String
    Public age As Integer
End Structure
 
' class cperson
Class cpersonne
    Public nom As String
    Public age As Integer
End Class
 
' a test module
Public Module test
    Sub Main()
        ' a person p1
        Dim sp1 As spersonne
        sp1.nom = "paul"
        sp1.age = 10
        Console.Out.WriteLine(("sp1=spersonne(" & sp1.nom & "," & sp1.age & ")"))
        ' a p2 person
        Dim sp2 As spersonne = sp1
        Console.Out.WriteLine(("sp2=spersonne(" & sp2.nom & "," & sp2.age & ")"))
        ' sp2 is modified
        sp2.nom = "nicole"
        sp2.age = 30
        ' checking sp1 and sp2
        Console.Out.WriteLine(("sp1=cpersonne(" & sp1.nom & "," & sp1.age & ")"))
        Console.Out.WriteLine(("sp2=cpersonne(" & sp2.nom & "," & sp2.age & ")"))
 
        ' a cperson cp1
        Dim cp1 As New cpersonne
        cp1.nom = "paul"
        cp1.age = 10
        Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
        ' a P2 person
        Dim cp2 As cpersonne = cp1
        Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
        ' P2 is modified
        cp2.nom = "nicole"
        cp2.age = 30
        ' p1 and P2 verification
        Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
        Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & 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=cpersonne(paul,10)
cP2=cpersonne(paul,10)
cP1=cpersonne(nicole,30)
cP2=cpersonne(nicole,30)

Whereas in the previous pages of this chapter we used a Person class, we now use a PersonStructure:


' structure spersonne
Structure spersonne
    Public nom 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
 
' structure person
Structure personne
    ' attributes
    Private _nom As String
    Private _age As Integer
 
    ' properties
    Public Property nom() As String
        Get
            Return _nom
        End Get
        Set(ByVal Value As String)
            _nom = 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
 
    ' Manufacturer
    Public Sub New(ByVal NOM As String, ByVal AGE As Integer)
        _nom = NOM
        _age = AGE
    End Sub    'New
 
    ' TOSTRING
    Public Overrides Function ToString() As String
        Return "personne(" & nom & "," & age & ")"
    End Function
End Structure
 
' a test module
Module test
    Sub Main()
        ' one person p1
        Dim p1 As New personne("paul", 10)
        Console.Out.WriteLine(("p1=" & p1.ToString))
        ' one person p2
        Dim p2 As personne = p1
        Console.Out.WriteLine(("p2=" & p2.ToString))
        ' p2 is modified
        p2.nom = "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=personne(paul,10)
p2=personne(paul,10)
p1=personne(paul,10)
p2=personne(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 moyenne() As Double
    Function écartType() As Double
End Interface

Any class implementing this interface will be declared as

public class C 
 Implements Istats
...
function moyenne() as Double Implements Istats.moyenne
...
end function
function écartType () as Double Implements Istats. écartType
...
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 élève
    Public _nom As String
    Public _note As Double
 
    ' manufacturer
    Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
        Me._nom = NOM
        Me._note = NOTE
    End Sub
End Structure
 
' class notes
Public Class notes
    ' attribute
    Protected _matière As String
    Protected _élèves() As élève
 
    ' manufacturer
    Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
        ' student & subject memorization
        Me._matière = MATIERE
        Me._élèves = ELEVES
    End Sub
 
    ' ToString
    Public Overrides Function ToString() As String
        Dim valeur As String = "matière=" + _matière + ", notes=("
        Dim i As Integer
        ' concatenate all the notes
        For i = 0 To (_élèves.Length - 1) - 1
            valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "],"
        Next i
        'final note
        If _élèves.Length <> 0 Then
            valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "]"
        End If
        valeur += ")"
        ' end
        Return valeur
    End Function
End Class

The notes class collects the grades for a class in a subject:


Public Class notes
    ' attribut
    Protected _matière As String
    Protected _élèves() As élève

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 élève
    Public _nom As String
    Public _note As Double
 
    ' manufacturer
    Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
        Me._nom = NOM
        Me._note = NOTE
    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 _moyenne As Double
    Private _écartType As Double

The notesStats class implements the following Istats interface:

Public Interface Istats
    Function moyenne() As Double
    Function écartType() 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 _moyenne As Double
    Private _écartType As Double
 
    ' manufacturer
    Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
        MyBase.New(MATIERE, ELEVES)
        ' average score calculation
        Dim somme As Double = 0
        Dim i As Integer
        For i = 0 To ELEVES.Length - 1
            somme += ELEVES(i)._note
        Next i
        If ELEVES.Length <> 0 Then
            _moyenne = somme / ELEVES.Length
        Else
            _moyenne = -1
        End If
        ' standard deviation
        Dim carrés As Double = 0
        For i = 0 To ELEVES.Length - 1
            carrés += Math.Pow(ELEVES(i)._note - _moyenne, 2)
        Next i
        If ELEVES.Length <> 0 Then
            _écartType = Math.Sqrt((carrés / ELEVES.Length))
        Else
            _écartType = -1
        End If
    End Sub
 
    ' ToString
    Public Overrides Function ToString() As String
        Return MyBase.ToString() & ",moyenne=" & _moyenne & ",écart-type=" & _écartType
    End Function    'ToString
 
    ' istats interface methods
    Public Function moyenne() As Double Implements Istats.moyenne
        ' makes the average score
        Return _moyenne
    End Function
 
    Public Function écartType() As Double Implements Istats.écartType
        ' makes the standard deviation
        Return _écartType
    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
 
Module test
    Sub Main()
        ' some students & notes
        Dim ELEVES() As élève = {New élève("paul", 14), New élève("nicole", 16), New élève("jacques", 18)}
        ' recorded in a notes object
        Dim anglais As New notes("anglais", ELEVES)
        ' and display
        Console.Out.WriteLine((anglais.ToString))
        ' idem with mean and standard deviation
        anglais = New notesStats("anglais", ELEVES)
        Console.Out.WriteLine((anglais.ToString))
    End Sub
End Module

gives the results:

matière=anglais, notes=([paul,14],[nicole,16],[jacques,18])
matière=anglais, notes=([paul,14],[nicole,16],[jacques,18]),moyenne=16,écart-type=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 Iexemple
    Function ajouter(ByVal i As Integer, ByVal j As Integer) As Integer
    Function soustraire(ByVal i As Integer, ByVal j As Integer) As Integer
End Interface
 
' a 1st class
Public Class classe1
    Implements Iexemple
 
    Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
        Return a + b + 10
    End Function
 
    Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
        Return a - b + 20
    End Function
End Class
 
'a 2nd class
Public Class classe2
    Implements Iexemple
 
    Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
        Return a + b + 100
    End Function
 
    Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
        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
Module test
 
    'calculate
    Sub calculer(ByVal i As Integer, ByVal j As Integer, ByVal inter As Iexemple)
        Console.Out.WriteLine(inter.ajouter(i, j))
        Console.Out.WriteLine(inter.soustraire(i, j))
    End Sub
 
    ' the Hand function
    Sub Main()
        ' creation of two objects class1 and class2
        Dim c1 As New classe1
        Dim c2 As New classe2
        ' static function calls calculate
        calculer(4, 3, c1)
        calculer(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 classe
    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 personne
        ' définition de la classe
...
  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
 
' creation of istia.st namespace
Namespace istia.st
    Public Class personne
        ' attributes
        Private prenom As String
        Private nom As String
        Private age As Integer
 
        ' method
        Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
            Me.prenom = P
            Me.nom = N
            Me.age = age
        End Sub
 
        ' method
        Public Sub identifie()
            Console.Out.WriteLine((prenom & "," & nom & "," & age))
        End Sub
    End Class
End Namespace

This class is compiled into person.dll:

dos>dir
11/03/2004  18:27               610 personne.vb
dos>vbc /t:library personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
12/03/2004  18:06             3 584 personne.dll
11/03/2004  18:27               610 personne.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
 
' pg test
Public Module test
    Sub Main()
        Dim p1 As New personne
        p1.initialise("Jean", "Dupont", 30)
        p1.identifie()
    End Sub
End Module

To avoid writing


        Dim p1 As New istia.st.personne

we have imported the istia.st namespace with an Imports clause:


Imports istia.st

Now let's compile the test program:

dos>dir
12/03/2004  18:06             3 584 personne.dll
11/03/2004  18:27               610 personne.vb
12/03/2004  18:05               254 test.vb
dos>vbc /r:personne.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
12/03/2004  18:06             3 584 personne.dll
11/03/2004  18:27               610 personne.vb
12/03/2004  18:08             3 072 test.exe
12/03/2004  18:05               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        49062

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 impot
    ' data required for tax calculation
    ' come from an external source
    Private limites(), coeffR(), coeffN() As Decimal
 
    ' manufacturer
    Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
        ' check that the 3 arrays have the same size
        Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length
        If Not OK Then
            Throw New Exception("Les 3 tableaux fournis n'ont pas la même taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
        End If
        ' it's good
        Me.limites = LIMITES
        Me.coeffR = COEFFR
        Me.coeffN = COEFFN
    End Sub
 
    ' tAX CALCULATION
    Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
        ' calculating the number of shares
        Dim nbParts As Decimal
        If marié Then
            nbParts = CDec(nbEnfants) / 2 + 2
        Else
            nbParts = CDec(nbEnfants) / 2 + 1
        End If
        If nbEnfants >= 3 Then
            nbParts += 0.5D
        End If
        ' calculation of taxable income & family quota
        Dim revenu As Decimal = 0.72D * salaire
        Dim QF As Decimal = revenu / nbParts
        ' tAX CALCULATION
        limites((limites.Length - 1)) = QF + 1
        Dim i As Integer = 0
        While QF > limites(i)
            i += 1
        End While
        Return CLng(revenu * 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
 
Module test
    Sub Main()
        ' interactive tax calculator
        ' the user enters three data points on the keyboard: married nbEnfants salary
        ' the program then displays the tax payable
        Const syntaxe As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
 
        ' data tables required for tax calculation
        Dim limites() 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}
 
        ' tax object creation
        Dim objImpôt As impot = Nothing
        Try
            objImpôt = New impot(limites, coeffR, coeffN)
        Catch ex As Exception
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
            Environment.Exit(1)
        End Try
        ' infinite loop
        Dim marié As String
        Dim nbEnfants As Integer
        Dim salaire As Long
        While True
            ' tax calculation parameters are requested
            Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
            Dim paramètres As String = Console.In.ReadLine().Trim()
            ' anything to do?
            If paramètres Is Nothing OrElse paramètres = "" Then
                Exit While
            End If
            ' check the number of arguments in the input line
            Dim erreur As Boolean = False
            Dim args As String() = paramètres.Split(Nothing)
            Dim nbParamètres As Integer = args.Length
            If nbParamètres <> 3 Then
                Console.Error.WriteLine(syntaxe)
                erreur = True
            End If
            ' checking parameter validity
            If Not erreur Then
                ' married
                marié = args(0).ToLower()
                If marié <> "o" And marié <> "n" Then
                    erreur = True
                End If
                ' nbEnfants
                Try
                    nbEnfants = Integer.Parse(args(1))
                    If nbEnfants < 0 Then
                        Throw New Exception
                    End If
                Catch
                    erreur = True
                End Try
                ' salary
                Try
                    salaire = Integer.Parse(args(2))
                    If salaire < 0 Then
                        Throw New Exception
                    End If
                Catch
                    erreur = True
                End Try
            End If
            ' if the parameters are correct - the tax is calculated
            If Not erreur Then
                Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire) & " F"))
            Else
                Console.Error.WriteLine(syntaxe)
            End If
        End While
    End Sub
End Module

Here is an example of the previous program in action:

dir>dir
12/03/2004  18:20             1 483 impots.vb
12/03/2004  18:21             2 805 test.vb
dos>vbc /t:library impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dir>dir
12/03/2004  18:24             4 096 impots.dll
12/03/2004  18:20             1 483 impots.vb
12/03/2004  18:21             2 805 test.vb
dos>vbc /r:impots.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
12/03/2004  18:24             4 096 impots.dll
12/03/2004  18:20             1 483 impots.vb
12/03/2004  18:26             6 144 test.exe
12/03/2004  18:21             2 805 test.vb
dos>test
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :x x x
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :