Skip to content

3. Klassen, Strukturen, Schnittstellen

3.1. Objekte am Beispiel

3.1.1. Allgemeiner Überblick

Wir werden nun die objektorientierte Programmierung anhand von Beispielen erkunden. Ein Objekt ist eine Entität, die Daten enthält, die seinen Zustand definieren (sogenannte Eigenschaften), sowie Funktionen (sogenannte Methoden). Ein Objekt wird auf der Grundlage einer Vorlage erstellt, die als Klasse bezeichnet wird:

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

Aus der vorherigen Klasse C1 können wir viele Objekte O1, O2, … erstellen. Alle verfügen über die Eigenschaften p1, p2, … und die Methoden m3, m4, … Sie weisen jedoch unterschiedliche Werte für ihre Eigenschaften pi auf und haben somit jeweils ihren eigenen Zustand. Analog dazu lautet die Deklaration

    dim i, j as integer

erzeugt zwei Objekte (der Begriff ist hier ungenau) vom Typ (Klasse) Integer. Ihre einzige Eigenschaft ist ihr Wert. Wenn O1 ein Objekt vom Typ C1 ist, bezieht sich O1.p1 auf die Eigenschaft p1 von O1 und O1.m1 auf die Methode m1 von O1. Betrachten wir ein erstes Objektmodell: die Klasse Person.

3.1.2. Definition der Klasse Person

Die Definition der Klasse Person lautet wie folgt:


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

Hier sehen wir die Definition einer Klasse, die einen Datentyp darstellt. Wenn wir Variablen dieses Typs erstellen, bezeichnen wir sie als Objekte oder Klasseninstanzen. Eine Klasse ist somit eine Vorlage, aus der Objekte erstellt werden. Die Elemente oder Felder einer Klasse können Daten (Attribute), Methoden (Funktionen) oder Eigenschaften sein. Eigenschaften sind spezielle Methoden, die zum Abrufen oder Festlegen des Werts der Attribute eines Objekts verwendet werden. Diese Felder können mit einem der folgenden drei Schlüsselwörter versehen sein:

privat
Auf ein privates Feld kann nur von den internen Methoden der Klasse zugegriffen werden
öffentlich
Ein öffentliches Feld ist für jede Funktion zugänglich, unabhängig davon, ob diese innerhalb der Klasse definiert ist oder nicht
geschützt
Ein geschütztes Feld ist nur für die internen Methoden der Klasse oder für ein abgeleitetes Objekt zugänglich (siehe das Konzept der Vererbung weiter unten).

Im Allgemeinen werden die Daten einer Klasse als privat deklariert, während ihre Methoden und Eigenschaften als öffentlich deklariert werden. Das bedeutet, dass der Benutzer eines Objekts (der Programmierer):

  • keinen direkten Zugriff auf die privaten Daten des Objekts hat
  • die öffentlichen Methoden des Objekts aufrufen kann, insbesondere diejenigen, die Zugriff auf dessen private Daten gewähren.

Die Syntax für die Deklaration einer Klasse lautet wie folgt:


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

Die Reihenfolge, in der private, geschützte und öffentliche Attribute deklariert werden, ist beliebig.

3.1.3. Die initialize-Methode

Kehren wir zu unserer Klasse [person] zurück, die wie folgt deklariert ist:


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

Welche Funktion hat die initialize-Methode? Da lastName, firstName und age private Daten der Person-Klasse sind, lauten die Anweisungen:

dim p1 as personne p1
p1.prenom="Jean"
p1.nom="Dupont"
p1.age=30

sind ungültig. Wir müssen ein Objekt vom Typ Person über eine öffentliche Methode initialisieren. Dies ist die Aufgabe der Methode initialize. Wir schreiben:

dim p1 as personne
p1.initialise("Jean","Dupont",30)

Die Syntax p1.initialize ist gültig, da initialize öffentlich ist.

3.1.4. Der new-Operator

Die Anweisungssequenz

dim p1 as personne
p1.initialise("Jean","Dupont",30)

ist falsch. Die Anweisung

dim p1 as personne

erklärt p1 als Referenz auf ein Objekt vom Typ Person. Dieses Objekt existiert noch nicht, daher ist p1 nicht initialisiert. Es ist, als hätten wir geschrieben:

dim p1 as personne=nothing

wobei wir mit dem Schlüsselwort nothing explizit angeben, dass die Variable p1 noch auf kein Objekt verweist. Wenn wir dann schreiben

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

, rufen wir die initialize-Methode des Objekts auf, auf das p1 verweist. Dieses Objekt existiert jedoch noch nicht, und der Compiler meldet einen Fehler. Damit p1 auf ein Objekt verweist, müssen Sie schreiben:

dim p1 as personne=new personne()

Dadurch wird ein nicht initialisiertes Person-Objekt erstellt: Die Attribute name und first_name, die Verweise auf String-Objekte sind, haben den Wert nothing, und age hat den Wert 0. Es findet also eine Standardinitialisierung statt. Da p1 nun auf ein Objekt verweist, wird die Initialisierungsanweisung für dieses Objekt

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

gültig.

3.1.5. Das Schlüsselwort Me

Sehen wir uns den Code für die Methode *Initialize* an:


    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    

Die Anweisung Me.firstName = P bedeutet, dass der Eigenschaft firstName des aktuellen Objekts (Me) der Wert P zugewiesen wird. Das Schlüsselwort Me bezieht sich auf das aktuelle Objekt: dasjenige, in dem die Methode ausgeführt wird. Woher wissen wir das? Schauen wir uns an, wie das von p1 referenzierte Objekt im aufrufenden Programm initialisiert wird:

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

Es ist die initialize-Methode des p1-Objekts, die aufgerufen wird. Wenn innerhalb dieser Methode auf das Me-Objekt verwiesen wird, bezieht es sich tatsächlich auf das p1-Objekt. Die initialize-Methode hätte auch wie folgt geschrieben werden können:


    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        prenom = P
        nom = N
        Me.age = age
    End Sub    

Wenn eine Methode eines Objekts auf ein Attribut A dieses Objekts verweist, ist die Notation Me.A impliziert. Sie muss explizit verwendet werden, wenn ein Konflikt zwischen Bezeichnern vorliegt. Dies ist bei der folgenden Anweisung der Fall:


Me.age=age;

wobei age sowohl auf ein Attribut des aktuellen Objekts als auch auf den von der Methode empfangenen Parameter age verweist. Die Mehrdeutigkeit muss dann durch die Bezugnahme auf das Attribut age als Me.age aufgelöst werden.

3.1.6. Ein Testprogramm

Hier ist ein kurzes Testprogramm:


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

und die erhaltenen Ergebnisse:

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. Verwendung einer kompilierten Klassendatei (Assembly)

Beachten Sie, dass es im vorherigen Beispiel zwei Klassen in unserem Testprogramm gibt: die Klassen „person“ und „test1“. Es gibt noch eine andere Vorgehensweise:

  • Kompilieren Sie die „Person“-Klasse in eine separate Datei, die als Assembly bezeichnet wird. Diese Datei hat die Erweiterung .dll

  • Wir kompilieren die Klasse „test1“, indem wir auf die Assembly verweisen, die die Klasse „person“ enthält.

Die beiden Quelldateien sehen nun wie folgt aus:

test.vb

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

' Optionen
Option Explicit On
Option Strict On

' Namespaces
Imports System

Public Class Person
    ' Attribute
    Private Vorname As String
    Private Nachname As String
    Private age As Integer

    ' Methode
    Public Sub Initialize(ByVal Vorname As String, ByVal Nachname As String, ByVal Alter As Integer)
        Me.Vorname = P
        Me.lastName = N
        Me.age = age
    End Sub    'initialisieren

    ' Methode
    Public Sub identifizieren()
        Console.Out.WriteLine((Vorname & "," & Nachname & "," & Alter))
    End Sub    'identify
End Class 'person

Die Klasse „Person“ wird durch die folgende Anweisung kompiliert:

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

Die Kompilierung erzeugte eine Datei namens personne2.dll. Es ist die Kompilierungsoption /t:library, die die Erstellung einer „Assembly“-Datei festlegt. Kompilieren wir nun die Datei test.vb:

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

Die Kompilierungsoption /r:personne2.dll teilt dem Compiler mit, dass er bestimmte Klassen in der Datei personne2.dll finden wird. Wenn er in der Quelldatei test.vb einen Verweis auf die Klasse Person findet – eine Klasse, die in der Quelldatei test.vb nicht deklariert ist –, sucht er in den .dll-Dateien, auf die die Option /r verweist, nach der Klasse Person. Er findet die Klasse Person hier in der Assembly Person2.dll. Wir hätten auch andere Klassen in diese Assembly aufnehmen können. Um bei der Kompilierung mehrere kompilierte Klassendateien zu verwenden, würden wir schreiben:

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

Die Ausführung des Programms test1.exe liefert folgende Ergebnisse:

dos>test
Jean,Dupont,30

3.1.8. Eine weitere Methode initialisiert

Fahren wir mit der Klasse „Person“ fort und fügen wir ihr die folgende Methode hinzu:


    ' method
    Public Sub initialise(ByVal P As personne)
        prenom = P.prenom
        nom = P.nom
        Me.age = P.age
    End Sub

Wir haben nun zwei Methoden namens Initialize: Dies ist zulässig, solange sie unterschiedliche Parameter akzeptieren. Das ist hier der Fall. Der Parameter ist nun eine Referenz P auf eine Person. Die Attribute der Person P werden dann dem aktuellen Objekt (Me) zugewiesen. Beachten Sie, dass die Initialize-Methode direkten Zugriff auf die Attribute des Objekts P hat, obwohl diese vom Typ „private“ sind. Dies gilt immer: Ein Objekt O1 der Klasse C hat immer Zugriff auf die Attribute von Objekten derselben Klasse C. Hier ist ein Test der neuen Person-Klasse, die wie zuvor erläutert in Person.dll kompiliert wurde:


' 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

und die Ergebnisse:

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

3.1.9. Konstruktoren der Klasse „Person“

Ein Konstruktor ist eine Prozedur namens New, die beim Erstellen des Objekts aufgerufen wird. Er wird im Allgemeinen zur Initialisierung des Objekts verwendet. Wenn eine Klasse einen Konstruktor hat, der n Argumente argi akzeptiert, kann die Deklaration und Initialisierung eines Objekts dieser Klasse wie folgt erfolgen:


        dim objet as classe =new classe(arg1,arg2, ... argn)

oder


        dim objet as classe

        objet=new classe(arg1,arg2, ... argn)

Wenn eine Klasse einen oder mehrere Konstruktoren hat, muss einer dieser Konstruktoren verwendet werden, um ein Objekt dieser Klasse zu erstellen. Wenn eine Klasse C keine Konstruktoren hat, verfügt sie über einen Standardkonstruktor, nämlich den Konstruktor ohne Parameter: public New(). Die Attribute des Objekts werden dann mit Standardwerten initialisiert. Genau das ist in den vorherigen Programmen geschehen, in denen wir geschrieben haben:

    dim p1 as personne
    p1=new personne

Erstellen wir zwei Konstruktoren für unsere Klasse „Person“:


' 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

Unsere beiden Konstruktoren rufen einfach die entsprechenden initialise-Methoden auf. Beachten Sie, dass der Compiler beispielsweise die Schreibweise initialise(P) in einem Konstruktor in Me.initialise(P) umwandelt. Im Konstruktor wird die initialise-Methode daher aufgerufen, um auf das Objekt zu wirken, auf das Me verweist, also auf das aktuelle Objekt, das gerade konstruiert wird. Hier ist ein kurzes Testprogramm:


' 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

und die erhaltenen Ergebnisse:

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

3.1.10. Objektreferenzen

Wir verwenden weiterhin dieselbe Person-Klasse. Das Testprogramm sieht nun wie folgt aus:


' 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

Die erhaltenen Ergebnisse lauten wie folgt:

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

Bei der Deklaration der Variablen p1 mit

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

verweist p1 auf das Objekt Person("Jean", "Dupont", 30), ist aber nicht das Objekt selbst. In C würde man sagen, dass es sich um einen Zeiger handelt, d. h. um die Adresse des erstellten Objekts. Wenn wir dann schreiben:

    p1=nothing

wird nicht das Objekt person("Jean", "Dupont", 30) verändert; es ist die Referenz p1, die ihren Wert ändert. Das Objekt person("Jean", "Dupont", 30) geht „verloren“, wenn es von keiner anderen Variablen referenziert wird.

Wenn wir schreiben:

dim p2 as personne=p1

initialisieren wir den Zeiger p2: Er „zeigt“ auf dasselbe Objekt (er verweist auf dasselbe Objekt) wie der Zeiger p1. Wenn wir also das Objekt ändern, auf das p1 „zeigt“ (oder auf das er verweist), ändern wir auch das Objekt, auf das p2 verweist.

Wenn wir schreiben:

dim p3 as personne =new personne(p1);

Es wird ein neues Objekt erstellt, das eine Kopie des Objekts ist, auf das p1 verweist. Auf dieses neue Objekt verweist p3. Wenn Sie das Objekt ändern, auf das p1 „zeigt“ (oder auf das es verweist), ändern Sie das Objekt, auf das p3 verweist, in keiner Weise. Das zeigen die Ergebnisse.

3.1.11. Temporäre Objekte

In einem Ausdruck können Sie den Konstruktor eines Objekts explizit aufrufen: Das Objekt wird erstellt, aber Sie können nicht darauf zugreifen (um es beispielsweise zu ändern). Dieses temporäre Objekt wird zum Auswerten des Ausdrucks erstellt und anschließend verworfen. Der von ihm belegte Speicherplatz wird später automatisch von einem Programm namens „Garbage Collector“ zurückgewonnen, dessen Aufgabe es ist, Speicherplatz zurückzugewinnen, der von Objekten belegt wird, auf die von Programmdaten nicht mehr verwiesen wird. Betrachten Sie das folgende neue Testprogramm:


' 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

und ändern wir die Konstruktoren der Person-Klasse so, dass sie eine Meldung anzeigen:


    ' 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

Wir erhalten folgende Ergebnisse:

dos>test
Constructeur personne(String, String, integer)
Constructeur personne(personne)
Jean,Dupont,30

die schrittweise Erstellung der beiden temporären Objekte zeigt.

3.1.12. Methoden zum Lesen und Schreiben privater Attribute

Wir fügen der Klasse „Person“ die erforderlichen Methoden hinzu, um den Zustand der Attribute der Objekte zu lesen oder zu ändern:


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

Wir testen die neue Klasse mit dem folgenden Programm:


' 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

und wir erhalten folgende Ergebnisse:

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

3.1.13. Eigenschaften

Es gibt noch eine weitere Möglichkeit, auf die Attribute einer Klasse zuzugreifen: durch das Erstellen von Eigenschaften. Diese ermöglichen es uns, private Attribute so zu manipulieren, als wären sie öffentlich. Betrachten Sie die folgende Person-Klasse, in der die vorherigen Getter und Setter durch Lese-Schreib-Eigenschaften ersetzt wurden:


' 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

Eine Eigenschaft ermöglicht es Ihnen, den Wert eines Attributs zu lesen (get) oder festzulegen (set). In unserem Beispiel haben wir den Attributnamen ein Unterstrich (_) vorangestellt, damit die Eigenschaften dieselben Namen wie die primitiven Attribute haben. Der Grund dafür ist, dass eine Eigenschaft nicht denselben Namen wie das von ihr verwaltete Attribut haben darf; andernfalls käme es zu einem Namenskonflikt innerhalb der Klasse. Wir haben unsere Attribute daher _firstName, _lastName und _age genannt und die Konstruktoren und Methoden entsprechend angepasst. Anschließend haben wir drei Eigenschaften erstellt: lastName, firstName und age. Eine Eigenschaft wird wie folgt deklariert:


    Public Property nom() As Type
        Get
...
        End Get
        Set(ByVal Value As Type)
...
        End Set    
    End Property

wobei Type der Typ des von der Eigenschaft verwalteten Attributs sein muss. Sie kann zwei Methoden namens get und set haben. Die get-Methode ist in der Regel dafür zuständig, den Wert des von ihr verwalteten Attributs zurückzugeben (sie könnte auch etwas anderes zurückgeben; nichts hindert sie daran). Die „set“-Methode erhält einen Parameter namens „value“, den sie normalerweise dem von ihr verwalteten Attribut zuweist. Sie kann diese Gelegenheit nutzen, um die Gültigkeit des empfangenen Werts zu überprüfen und gegebenenfalls eine Ausnahme auszulösen, wenn der Wert ungültig ist. Genau das geschieht hier für das Alter.

Wie werden diese get- und set-Methoden aufgerufen? Betrachten Sie das folgende Testprogramm:


' 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 der Anweisung

        Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))

versuchen wir, die Werte der Eigenschaften first_name, last_name und age der Person P abzurufen. Es ist die get-Methode dieser Eigenschaften, die dann aufgerufen wird und den Wert des von ihr verwalteten Attributs zurückgibt.

In der Anweisung

        P.age = 56

wollen wir den Wert der Eigenschaft „age“ festlegen. Die Set-Methode dieser Eigenschaft wird dann aufgerufen. Sie erhält den Wert 56 als Wertparameter.

Eine Eigenschaft P einer Klasse C, die nur die get-Methode definiert, wird als schreibgeschützt bezeichnet. Wenn c ein Objekt der Klasse C ist, wird die Operation c.P=value vom Compiler abgelehnt.

Die Ausführung des vorangegangenen Testprogramms liefert folgende Ergebnisse:

P=(Jean,Michelin,34)
P=(Jean,Michelin,56)
âge (-4) invalide

Eigenschaften ermöglichen es uns daher, private Attribute so zu manipulieren, als wären sie öffentlich.

3.1.14. Klassenmethoden und -attribute

Angenommen, wir möchten die Anzahl der in einer Anwendung erstellten [person]-Objekte zählen. Wir könnten selbst einen Zähler verwalten, aber dabei besteht die Gefahr, dass wir temporäre Objekte vergessen, die hier und da erstellt werden. Es erscheint sicherer, in die Konstruktoren der [person]-Klasse eine Anweisung einzufügen, die einen Zähler inkrementiert. Das Problem besteht darin, eine Referenz auf diesen Zähler zu übergeben, damit der Konstruktor ihn inkrementieren kann: Wir müssen ihnen einen neuen Parameter übergeben. Wir können den Zähler auch in die Klassendefinition aufnehmen. Da es sich um ein Attribut der Klasse selbst und nicht um ein bestimmtes Objekt dieser Klasse handelt, deklarieren wir es anders, indem wir das Schlüsselwort „Shared“ verwenden:

    Private Shared _nbPersonnes As Long = 0

Um darauf zu verweisen, schreiben wir person.\_nbPeople, um zu verdeutlichen, dass es sich um ein Attribut der Klasse Person* selbst handelt. Hier haben wir ein privates Attribut erstellt, auf das von außerhalb der Klasse nicht direkt zugegriffen werden kann. Wir erstellen daher eine öffentliche Eigenschaft, um Zugriff auf das Klassenattribut nbPeople zu gewähren. Um den Wert von nbPeople zurückzugeben, benötigt die Get-Methode dieser Eigenschaft kein spezifisches Person-Objekt: Tatsächlich ist _nbPeople nicht das Attribut eines bestimmten Objekts, sondern das Attribut der gesamten Klasse. Daher benötigen wir eine Eigenschaft, die ebenfalls als Shared* deklariert ist:

    Public Shared ReadOnly Property nbPersonnes() As Long
        Get
            Return _nbPersonnes
        End Get
    End Property

die von außen mit der Syntax person.nbPeople aufgerufen wird. Die Eigenschaft wird als schreibgeschützt (ReadOnly) deklariert, da sie keine Set-Methode bereitstellt. Hier ist ein Beispiel. Die Person-Klasse sieht nun wie folgt aus:


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

Mit dem folgenden Programm:


' 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

Es werden folgende Ergebnisse erhalten:

    Nombre de personnes créées : 2

3.1.15. Übergabe eines Objekts an eine Funktion

Wir haben bereits erwähnt, dass VB.NET standardmäßig tatsächliche Funktionsparameter als Wert übergibt: Die Werte der tatsächlichen Parameter werden in die formalen Parameter kopiert. Lassen Sie sich beim Umgang mit einem Objekt nicht von der gängigen sprachlichen Konvention täuschen, von einem „Objekt“ statt von einer „Objektreferenz“ zu sprechen. Ein Objekt wird ausschließlich über eine Referenz (einen Zeiger) darauf manipuliert. An eine Funktion übergeben wird daher nicht das Objekt selbst, sondern eine Referenz auf dieses Objekt. Es ist somit der Wert der Referenz – und nicht der Wert des Objekts selbst –, der in den formalen Parameter kopiert wird: Es wird kein neues Objekt erstellt. Wird eine Objektreferenz R1 an eine Funktion übergeben, wird sie in den entsprechenden formalen Parameter R2 kopiert. Somit verweisen die Referenzen R2 und R1 auf dasselbe Objekt. Wenn die Funktion das von R2 referenzierte Objekt verändert, verändert sie offensichtlich auch das von R1 referenzierte, da es sich um dasselbe Objekt handelt.

Dies wird durch das folgende Beispiel veranschaulicht:


' 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

Die erhaltenen Ergebnisse lauten wie folgt:

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

Wir sehen, dass nur ein Objekt erstellt wird: das der Person p1 in der Main-Prozedur, und dass das Objekt tatsächlich durch die modify-Funktion geändert wurde.

3.1.16. Ein Array von Personen

Ein Objekt ist ein Datenelement wie jedes andere, und als solches können mehrere Objekte in einem Array zusammengefasst werden:


' 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

Die Anweisung Dim friends(2) As Person erstellt ein Array mit 3 Elementen vom Typ Person. Diese 3 Elemente werden hier mit dem Wert Nothing initialisiert, was bedeutet, dass sie auf keine Objekte verweisen. Auch hier sprechen wir im technischen Sinne von einem „Array von Objekten“, obwohl es sich eigentlich nur um ein Array von Objektreferenzen handelt. Das Erstellen des Objekt-Arrays, das selbst ein Objekt ist, erzeugt keine Objekte vom Typ seiner Elemente: Dies muss später erfolgen. Es werden folgende Ergebnisse erzielt:

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

3.2. Vererbung durch Beispiel

3.2.1. Übersicht

Hier besprechen wir das Konzept der Vererbung. Der Zweck der Vererbung besteht darin, eine bestehende Klasse so „anzupassen“, dass sie unseren Anforderungen entspricht. Angenommen, wir möchten eine Klasse „Lehrer“ erstellen: Ein Lehrer ist eine bestimmte Art von Person. Er verfügt über Attribute, die andere Personen nicht haben, beispielsweise das Fach, das er unterrichtet. Er hat aber auch die Attribute einer beliebigen Person: Vorname, Nachname und Alter. Ein Lehrer ist daher ein vollwertiges Mitglied der Klasse „Person“, verfügt jedoch über zusätzliche Attribute. Anstatt eine Klasse „Lehrer“ von Grund auf neu zu schreiben, würden wir es vorziehen, auf der bestehenden Klasse „Person“ aufzubauen und diese an die spezifischen Eigenschaften von Lehrern anzupassen. Genau das ermöglicht uns das Konzept der Vererbung. Um auszudrücken, dass die Klasse „Lehrer“ die Eigenschaften der Klasse „Person“ erbt, schreiben wir:


Public Class enseignant
    Inherits personne

Beachten Sie die spezielle zweizeilige Syntax. Die Klasse „Person“ wird als übergeordnete (oder Basis-)Klasse bezeichnet, die Klasse „Lehrer“ als abgeleitete (oder untergeordnete) Klasse. Ein „Lehrer“-Objekt verfügt über alle Eigenschaften eines „Person“-Objekts: Es hat dieselben Attribute und Methoden. Diese Attribute und Methoden der übergeordneten Klasse werden in der Definition der untergeordneten Klasse nicht wiederholt; wir geben lediglich die Attribute und Methoden an, die von der untergeordneten Klasse hinzugefügt wurden. Wir gehen davon aus, dass die Klasse „Person“ wie folgt definiert ist:


' 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

Die Methode „Identify“ wurde durch die schreibgeschützte Eigenschaft „Identity“ ersetzt, die die Person identifiziert. Wir erstellen eine Klasse „Teacher“, die von der Klasse „Person“ erbt:


' 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 

Die Klasse „Teacher“ erweitert die Methoden und Attribute der Klasse „Person“ um Folgendes:

  • ein Attribut `section`, das die Nummer der Abteilung angibt, zu der der Lehrer innerhalb des Lehrerkollegiums gehört (in der Regel eine Abteilung pro Fach)
  • einen neuen Konstruktor, der alle Attribute eines Lehrers initialisiert

Die Deklaration


Public Class enseignant
    Inherits personne

bedeutet, dass die Klasse „Teacher“ von der Klasse „Person“ abgeleitet ist.

3.2.2. Erstellen eines Teacher-Objekts

Der Konstruktor für die Klasse „Teacher“ lautet wie folgt:


    ' 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    

Die Deklaration


    Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)

gibt an, dass der Konstruktor vier Parameter entgegennimmt: P, N, age und section. Er muss drei davon (P, N, age) an seine Basisklasse weitergeben, in diesem Fall an die Klasse Person. Wir wissen, dass diese Klasse über einen Konstruktor Person(string, string, int) verfügt, der es uns ermöglicht, ein Person-Objekt unter Verwendung der übergebenen Parameter (P, N, age) zu erstellen. Die Klasse [Teacher] übergibt die Parameter (P, N, age) wie folgt an ihre Basisklasse:


        MyBase.New(P, N, age)

Sobald die Basisklasse instanziiert wurde, wird die Erstellung des Teacher-Objekts mit der Ausführung des Konstruktor-Körpers fortgesetzt:


        Me._section = section

Zusammenfassend lässt sich sagen, dass der Konstruktor einer abgeleiteten Klasse:

  • übermittelt an seine Basisklasse die Parameter, die er benötigt, um sich selbst zu konstruieren
  • verwendet die anderen Parameter, um seine eigenen Attribute zu initialisieren

Wir hätten auch schreiben können:


    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    

Das ist unmöglich. Die Klasse „Person“ hat ihre drei Felder – _firstName, _lastName und _age – als privat deklariert. Nur Objekte derselben Klasse haben direkten Zugriff auf diese Felder. Alle anderen Objekte, einschließlich untergeordneter Objekte wie in diesem Fall, müssen öffentliche Methoden verwenden, um auf sie zuzugreifen. Dies wäre anders gewesen, wenn die Klasse „Person“ die drei Felder als geschützt deklariert hätte: Dann hätten abgeleitete Klassen direkten Zugriff auf die drei Felder gehabt. In unserem Beispiel war die Verwendung des Konstruktors der übergeordneten Klasse daher die richtige Lösung und entspricht dem Standardansatz: Beim Erstellen eines untergeordneten Objekts rufen wir zunächst den Konstruktor des übergeordneten Objekts auf und führen anschließend die für das untergeordnete Objekt spezifischen Initialisierungen durch (Abschnitt in unserem Beispiel).

Kompilieren wir nun die Klassen „Person“ und „Teacher“ zu 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

Beachten Sie, dass wir zum Kompilieren der untergeordneten Klasse „Teacher“ auf die Datei „person.dll“ verweisen mussten, die die Klasse „Person“ enthält. Versuchen wir es mit einem ersten Testprogramm:


' 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

Dieses Programm erstellt lediglich ein Teacher-Objekt (new) und identifiziert es. Die Teacher-Klasse verfügt nicht über eine Identity-Methode, aber ihre übergeordnete Klasse hat eine, die zudem öffentlich ist: Durch Vererbung wird sie zu einer öffentlichen Methode der Teacher-Klasse. Die erhaltenen Ergebnisse lauten wie folgt:

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)

Wir sehen, dass:

  • ein Person-Objekt vor dem Lehrer-Objekt erstellt wurde
  • die ermittelte Identität ist die des Objekts „Person“

3.2.3. Überladen einer Methode oder Eigenschaft

Im vorherigen Beispiel hatten wir die Identität der Person als Teil des Lehrers, aber es fehlen einige für die Klasse „Teacher“ spezifische Informationen (der Abschnitt). Wir müssen daher eine Eigenschaft schreiben, die es uns ermöglicht, den Lehrer zu identifizieren:


' 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

Die identity-Methode der Teacher-Klasse stützt sich auf die identity-Methode ihrer übergeordneten Klasse (MyBase.identity), um den Teil „person“ anzuzeigen, und ergänzt diesen anschließend um das Feld _section, das spezifisch für die Teacher-Klasse ist. Beachten Sie die Deklaration der identity-Eigenschaft:


    Public Shadows ReadOnly Property identite() As String

Dies bedeutet, dass die Eigenschaft „identity“ die gleichnamige Methode „verbirgt“, die möglicherweise in der übergeordneten Klasse vorhanden ist. Betrachten wir ein „Teacher“-Objekt E. Dieses Objekt enthält ein „Person“-Objekt:

Die Eigenschaft „identity“ ist sowohl in der Klasse „Teacher“ als auch in ihrer übergeordneten Klasse „Person“ definiert. In der untergeordneten Klasse „Teacher“ muss der Eigenschaft „identity“ das Schlüsselwort „shadows“ vorangestellt werden, um anzugeben, dass wir eine neue Eigenschaft „identity“ für die Klasse „Teacher“ neu definieren.


    Public Shadows ReadOnly Property identite() As String

Die Klasse „Teacher“ verfügt nun über zwei Identitäts-Eigenschaften:

  • die von der übergeordneten Klasse „Person“ geerbte
  • eine eigene

Wenn E ein „Teacher“-Objekt ist, verweist E.identity auf die identity-Methode der „Teacher“-Klasse. Wir sagen, dass die identity-Eigenschaft der übergeordneten Klasse durch die identity-Eigenschaft der untergeordneten Klasse „überschrieben“ wird. Allgemein gilt: Wenn O ein Objekt und M eine Methode ist, sucht das System zur Ausführung der Methode O.M in der folgenden Reihenfolge nach einer Methode M:

  • in der Klasse des Objekts O
  • in seiner übergeordneten Klasse, falls vorhanden
  • in der übergeordneten Klasse seiner übergeordneten Klasse, falls diese existiert
  • usw.

Die Vererbung ermöglicht es daher, Methoden/Eigenschaften mit demselben Namen in der übergeordneten Klasse in der untergeordneten Klasse neu zu definieren. Dadurch kann die untergeordnete Klasse an ihre eigenen Bedürfnisse angepasst werden. In Kombination mit dem Polymorphismus, auf den wir gleich noch eingehen werden, ist die Überladung von Methoden/Eigenschaften der Hauptvorteil der Vererbung. Betrachten wir dasselbe Beispiel wie zuvor:


' 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

Die diesmal erzielten Ergebnisse lauten wie folgt:

Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Jean,Dupont,30),27)

3.2.4. Polymorphismus

Betrachten wir eine Klassenhierarchie: C0 C1 C2 … Cn, wobei Ci Cj angibt, dass die Klasse Cj von der Klasse Ci abgeleitet ist. Dies bedeutet, dass die Klasse Cj alle Eigenschaften der Klasse Ci sowie zusätzliche Eigenschaften besitzt. Seien Oi Objekte vom Typ Ci. Es ist zulässig zu schreiben:

    Oi=Oj avec j>i

Tatsächlich besitzt die Klasse Cj durch Vererbung alle Eigenschaften der Klasse Ci sowie zusätzliche Eigenschaften. Daher enthält ein Objekt Oj vom Typ Cj ein Objekt vom Typ Ci. Die Operation

Oi=Oj

bedeutet, dass Oi ein Verweis auf das im Objekt Oj enthaltene Objekt vom Typ Ci ist.

Die Tatsache, dass eine Variable Oi der Klasse Ci nicht nur auf ein Objekt der Klasse Ci, sondern auf jedes von der Klasse Ci abgeleitete Objekt verweisen kann, wird als Polymorphismus bezeichnet: die Fähigkeit einer Variablen, auf Objekte unterschiedlicher Typen zu verweisen. Betrachten wir ein Beispiel und nehmen wir die folgende Funktion, die unabhängig von jeder Klasse ist:


    Sub affiche(ByVal p As personne)

Wir könnten genauso gut schreiben

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

als

    dim e as enseignant
    ...
    affiche(e);

Im letzteren Fall erhält der formale Parameter vom Typ „Person“ in der Anzeigefunktion einen Wert vom Typ „Lehrer“. Da der Typ „Lehrer“ vom Typ „Person“ abgeleitet ist, ist dies zulässig.

3.2.5. Neudefinition und Polymorphismus

Vervollständigen wir unsere Anzeige-Prozedur:


    Sub affiche(ByVal p As personne)
        ' displays identity of p
        Console.Out.WriteLine(p.identite)
    End Sub    

Die Methode p.identity gibt eine Zeichenfolge zurück, die das Person-Objekt identifiziert. Was passiert in unserem vorherigen Beispiel, wenn es sich um ein Teacher-Objekt handelt:


        Dim e As New enseignant("Lucile", "Dumas", 56, 61)
        affiche(e)

Sehen wir uns das folgende Beispiel an:


' 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

Die erzielten Ergebnisse lauten wie folgt:

Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Lucile,Dumas,56)
Construction personne(string, string, int)
personne(Jean,Dupont,30)

Die Ausführung zeigt, dass die Anweisung p.identity jedes Mal die Identitäts-Eigenschaft einer Person auslöste: zuerst die in dem Lehrer e enthaltene Person, dann die Person p selbst. Sie passte sich nicht an das Objekt an, das tatsächlich als Parameter an display übergeben wurde. Wir hätten es vorgezogen, die vollständige Identität des Lehrers e zu erhalten. Um dies zu erreichen, hätte die Notation p.identity auf die Identitätseigenschaft des Objekts verweisen müssen, auf das p tatsächlich zeigt, anstatt auf die Identitätseigenschaft des Teils „person“ des Objekts, auf das p tatsächlich zeigt. Es ist möglich, dieses Ergebnis zu erzielen, indem man identity als überschreibbare Eigenschaft in der Basisklasse Person deklariert:


    Public Overridable ReadOnly Property identite() As String
        Get
            Return "personne(" & _prenom & "," & _nom & "," & age & ")"
        End Get
    End Property

Das Schlüsselwort overridable macht *identite* zu einer verfeinerbaren oder virtuellen Eigenschaft. Dieses Schlüsselwort kann auch auf Methoden angewendet werden. Unterklassen, die eine virtuelle Eigenschaft oder Methode neu definieren, müssen das Schlüsselwort overrides anstelle von shadows verwenden, um ihre neu definierte Eigenschaft oder Methode zu kennzeichnen. Daher ist in der Klasse enseignant die Eigenschaft identite wie folgt definiert:


    ' overload property identity
    Public Overrides ReadOnly Property identite() As String
        Get
            Return "enseignant(" & MyBase.identite & "," & _section & ")"
        End Get
    End Property

Das Testprogramm:


' 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

ergibt dann folgende Ergebnisse:

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)

Diesmal haben wir erfolgreich die vollständige Identität des Lehrers ermittelt. Definieren wir nun eine Methode statt einer Eigenschaft neu. Die Object-Klasse ist die „übergeordnete“ Klasse aller VB.NET-Klassen. Wenn wir also schreiben:

    public class personne

schreiben wir implizit:

    public class personne 
         inherits object

Die Klasse „object“ definiert eine virtuelle ToString-Methode:

Image

Die ToString-Methode gibt den Namen der Klasse zurück, zu der das Objekt gehört, wie im folgenden Beispiel gezeigt:


' 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

Die Ergebnisse lauten wie folgt:

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

Beachten Sie, dass wir zwar die ToString-Methode in den Klassen Person und Lehrer nicht überschrieben haben, aber dennoch sehen können, dass die ToString-Methode der Klasse Object weiterhin in der Lage ist, den tatsächlichen Klassennamen des Objekts anzuzeigen. Überschreiben wir nun die ToString-Methode in den Klassen Person und Lehrer:


    ' ToString
    Public Overrides Function ToString() As String
        ' we return the identity property
        Return identite
    End Function

Die Definition ist in beiden Klassen identisch. Betrachten Sie das folgende Testprogramm:


' 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

Die Ausführungsergebnisse lauten wie folgt:

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. Definition eines Indexers für eine Klasse

Betrachten Sie die in der .NET-Plattform vordefinierte Klasse [ArrayList]. Diese Klasse ermöglicht es Ihnen, Objekte in einer Liste zu speichern. Sie gehört zum Namespace [System.Collections]. Im folgenden Beispiel verwenden wir diese Klasse, um eine Liste von Personen (im weiteren Sinne) zu speichern:


' 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

Hier einige Informationen zu bestimmten Eigenschaften und Methoden der Klasse [ArrayList]:

Count
Eigenschaft, die die Anzahl der Elemente in der Liste zurückgibt
Add(Object)
Methode zum Hinzufügen eines Objekts zur Liste
Item(Integer i)
Methode, die das i-te Element der Liste zurückgibt

Wir stellen fest, dass wir, um das i-te Element der Liste zu erhalten, nicht [list.Item(i)] geschrieben haben, sondern direkt [list(i)], was auf den ersten Blick falsch erscheint. Dies ist jedoch möglich, da die Klasse [ArrayList] eine Standard-Eigenschaft [Item] mit einer Syntax definiert, die in etwa wie folgt aussieht:


    Default Public Property Item(ByVal i As Integer) As Object
        Get
...
        End Get
        Set(ByVal Value As personne)
...
        End Set    
    End Property

Wenn der Compiler auf die Notation [list(i)] stößt, prüft er, ob die Klasse [ArrayList] eine Eigenschaft mit der folgenden Signatur definiert hat:


    Default Public Property Proc(ByVal var As Integer) As Type

Hier findet es die Prozedur [Item]. Anschließend wandelt es die Notation [list(i)] in [list.Item(i)] um. Die Eigenschaft [Item] ist die standardmäßige indizierte Eigenschaft der Klasse [ArrayList]. Die Ausführung des vorangegangenen Programms liefert folgende Ergebnisse:

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)

Erstellen wir eine Klasse namens [listOfPeople], die eine Liste von Personen darstellt, also eine spezielle Liste, die sich nahtlos aus der Klasse [ArrayList] ableiten lässt:


' 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

Die Klasse verfügt über die folgenden Methoden und Eigenschaften:

ToString
gibt eine Zeichenfolge zurück, die den Inhalt der Liste „darstellt“
Add(person)
Methode zum Hinzufügen einer Person zur Liste
Item(Integer i)
Standardmäßige indizierte Eigenschaft, die die i-te Person in der Liste zurückgibt

Betrachten wir die neuen Funktionen:

Es wird eine neue Methode [Add] erstellt.


    ' 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

In der übergeordneten Klasse [ArrayList] gibt es bereits eine Prozedur mit derselben Signatur, daher das Schlüsselwort [Shadows], um anzugeben, dass die neue Prozedur diejenige in der übergeordneten Klasse ersetzt. Die [Add]-Methode der Unterklasse prüft mithilfe der [TypeOf]-Funktion, ob das hinzugefügte Objekt tatsächlich vom Typ [person] oder einem abgeleiteten Typ ist. Ist dies nicht der Fall, wird mit der [Throw]-Anweisung eine Ausnahme ausgelöst. Ist das hinzugefügte Objekt tatsächlich vom Typ [person], wird die [Add]-Methode der Basisklasse verwendet, um das Hinzufügen durchzuführen.

Es wird eine indizierte Eigenschaft erstellt:


    ' 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

In der Basisklasse gibt es bereits eine Standard-Eigenschaft namens [Item] mit derselben Signatur. Daher müssen wir das Schlüsselwort [Shadows] verwenden, um anzugeben, dass die neue indizierte Eigenschaft [Index] die Eigenschaft [Item] der Basisklasse „ausblendet“. Beachten Sie, dass dies auch dann gilt, wenn die beiden Eigenschaften nicht denselben Namen haben. Die Eigenschaft [Index] ermöglicht es Ihnen, auf die Person am Index i in der Liste zu verweisen. Sie stützt sich auf die Eigenschaft [Item] der Basisklasse, um auf das Element am Index i des zugrunde liegenden [ArrayList]-Objekts zuzugreifen. Es werden Typänderungen vorgenommen, um der Tatsache Rechnung zu tragen, dass die Eigenschaft [Item] mit Elementen vom Typ [Object] arbeitet, während die Eigenschaft [Index] mit Elementen vom Typ [Person] arbeitet.

Schließlich überschreiben wir die [ToString]-Methode der [ArrayList]-Klasse:


    ' 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

Diese Methode gibt eine Zeichenfolge der Form „(e1,e2,...,en)“ zurück, wobei e1, e2, ..., en die Elemente der Liste sind. Beachten Sie die Notation [Me(i)], die sich auf das i-te Element des aktuellen Objekts [Me] bezieht. Dies ist die standardmäßig verwendete indizierte Eigenschaft. Somit entspricht [Me(i)] [Me.Index(i)].

Der Klassencode wird in die Datei [lstpersonnes2.vb] eingefügt und kompiliert:

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

Ein Testprogramm wird erstellt:


' 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

Es wird kompiliert:

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

dann ausgeführt:

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

Man möchte vielleicht schreiben

dim p as personne=l("nom")

wobei l vom Typ [listOfPeople] wäre. Hier möchten wir die Liste l nicht anhand einer Elementnummer, sondern anhand des Namens einer Person indizieren. Dazu definieren wir eine neue standardmäßig indizierte Eigenschaft:


    ' 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

Die erste Zeile


    Default Public Shadows ReadOnly Property Index(ByVal N As String) As Integer

gibt an, dass wir eine neue standardmäßige indizierte Eigenschaft erstellen. Alle Standard-Eigenschaften müssen denselben Namen haben, in diesem Fall [Index]. Die neue [Index]-Eigenschaft indiziert die PeopleList-Klasse anhand einer Zeichenfolge N. Das Ergebnis von PeopleList(N) ist eine Ganzzahl. Diese Ganzzahl gibt die Position der Person mit dem Namen N in der Liste an oder -1, falls diese Person nicht in der Liste enthalten ist. Wir definieren nur die Get-Eigenschaft und verhindern damit die Zuweisung listOfPeople("name")=value, die die Definition der Set-Eigenschaft erfordert hätte. Daher das Schlüsselwort [ReadOnly]. Das Schlüsselwort [Shadows] ist erforderlich, um die Standard-Eigenschaft der Basisklasse auszublenden (auch wenn sie nicht dieselbe Signatur hat).

Im Hauptteil der get-Methode durchlaufen wir die Liste der Personen und suchen nach dem als Parameter übergebenen Namen N. Wenn wir ihn an Position i finden, geben wir i zurück; andernfalls geben wir -1 zurück.

Ein neues Testprogramm könnte wie folgt aussehen:


' 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

Die Ausführung liefert folgende Ergebnisse:

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. Strukturen

Die VB.NET-Struktur leitet sich direkt von der C-Struktur ab und ähnelt stark einer Klasse. Eine Struktur wird wie folgt definiert:


Structure spersonne
' attributs
    ...
' propriétés
...
' constructeurs
...
' méthodes
End Structure

Trotz Ähnlichkeiten in der Syntax gibt es erhebliche Unterschiede zwischen Klassen und Strukturen. So gibt es beispielsweise bei Strukturen kein Konzept der Vererbung. Wenn wir eine Klasse schreiben, die nicht von einer anderen abgeleitet werden soll, welche Unterschiede zwischen Strukturen und Klassen helfen uns dann bei der Entscheidung zwischen den beiden? Schauen wir uns das folgende Beispiel an, um das herauszufinden:


' 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

Wenn wir dieses Programm ausführen, erhalten wir folgende Ergebnisse:

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)

Während wir auf den vorherigen Seiten dieses Kapitels eine Person-Klasse verwendet haben, verwenden wir nun eine PersonStructure:


' structure spersonne
Structure spersonne
    Public nom As String
    Public age As Integer
End Structure

Die Deklaration


        Dim sp1 As spersonne

erstellt eine Struktur (name, age) und der Wert von sp1 ist diese Struktur selbst.

Die Deklaration


        Dim cp1 As New cpersonne

erstellt ein Objekt [cpersonne] (das in etwa unserer Struktur entspricht), und cp1 ist dann die Adresse (die Referenz) dieses Objekts.

Zusammenfassend lässt sich sagen

  • Im Fall der Struktur ist der Wert von sp1 die Struktur selbst
  • im Fall einer Klasse ist der Wert von p1 die Adresse des erstellten Objekts

Wenn wir im Programm schreiben


        Dim sp2 As spersonne = sp1

wird eine neue Struktur (Name, Alter) erstellt und mit dem Wert von p1 initialisiert, d. h. mit der Struktur selbst.

Die Struktur von sp1 wird somit in sp2 dupliziert. Dies ist eine Wertkopie.

Die Anweisung


        Dim cp2 As cpersonne = cp1

funktioniert anders. Der Wert von cp1 wird in cp2 kopiert, aber da dieser Wert eigentlich die Adresse des Objekts ist, wird das Objekt selbst nicht dupliziert. Es gibt lediglich zwei Verweise, die darauf verweisen:

Im Fall der Struktur wird der Wert von sp1 nicht verändert, wenn wir den Wert von sp2 ändern, wie das Programm zeigt. Im Fall des Objekts wird das Objekt, auf das cp1 zeigt, verändert, wenn wir das Objekt ändern, auf das cp2 zeigt, da es sich um dasselbe Objekt handelt. Dies wird ebenfalls durch die Ergebnisse des Programms veranschaulicht.

Aus diesen Erläuterungen können wir daher folgern, dass:

  • der Wert einer Variablen vom Typ Struktur die Struktur selbst ist
  • der Wert einer Variablen vom Typ Objekt ist die Adresse des Objekts, auf das sie zeigt

Sobald man diesen grundlegenden Unterschied verstanden hat, erweist sich die Struktur als einer Klasse sehr ähnlich, wie das folgende neue Beispiel zeigt:


' 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

Es werden folgende Ausführungsergebnisse erhalten:

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

Der einzige nennenswerte Unterschied zwischen einer Struktur und einer Klasse besteht darin, dass bei einer Klasse die Objekte p1 und p2 am Ende des Programms denselben Wert gehabt hätten, nämlich den von p2.

3.5. Schnittstellen

Eine Schnittstelle ist eine Sammlung von Methoden- oder Eigenschaftsprototypen, die einen Vertrag bilden. Eine Klasse, die sich entscheidet, eine Schnittstelle zu implementieren, verpflichtet sich, eine Implementierung aller in der Schnittstelle definierten Methoden bereitzustellen. Der Compiler überprüft diese Implementierung. Hier ist zum Beispiel die Definition der Schnittstelle Istats:

Public Interface Istats
    Function moyenne() As Double
    Function écartType() As Double
End Interface

Jede Klasse, die diese Schnittstelle implementiert, wird wie folgt deklariert:

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

Die Methoden [mean] und [standardDev] müssen in der Klasse C definiert werden. Betrachten Sie den folgenden 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

Die Klasse „notes“ erfasst die Noten für eine Klasse in einem Fach:


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

Die Attribute sind als „protected“ deklariert, damit von einer abgeleiteten Klasse aus auf sie zugegriffen werden kann. Der Typ „student“ ist eine Struktur, die den Namen des Schülers und dessen Note in dem jeweiligen Fach speichert:


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

Wir beschließen, diese Notes-Klasse in eine notesStats-Klasse abzuleiten, die zwei zusätzliche Attribute haben soll: den Durchschnitt und die Standardabweichung der Noten:


Public Class notesStats
    Inherits notes
    Implements Istats
 
    ' attributes
    Private _moyenne As Double
    Private _écartType As Double

Die Klasse „notesStats“ implementiert die folgende Istats-Schnittstelle:

Public Interface Istats
    Function moyenne() As Double
    Function écartType() As Double
End Interface

Das bedeutet, dass die Klasse notesStats zwei Methoden namens average und standardDeviation mit den in der Schnittstelle Istats angegebenen Signaturen haben muss. Die Klasse notesStats sieht wie folgt aus:


' 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

Der Mittelwert _mean und die Standardabweichung _standardDev werden bei der Erstellung des Objekts berechnet. Daher geben die Methoden mean und standardDev einfach die Werte der Attribute _mean und _standardDev zurück. Beide Methoden geben -1 zurück, wenn das Array student leer ist.

Die folgende Testklasse:


' 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

ergibt folgende Ergebnisse:

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

Die Klasse notesStats hätte die Methoden average und standardDev sehr wohl selbst implementieren können, ohne anzugeben, dass sie die Schnittstelle Istats implementiert. Was ist der Sinn von Schnittstellen? Er ist folgender: Eine Funktion kann als formalen Parameter einen Wert vom Typ I akzeptieren. Jedes Objekt der Klasse C, das die Schnittstelle I implementiert, kann dann ein tatsächlicher Parameter dieser Funktion sein. Betrachten Sie das folgende Beispiel:


' 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

Die Schnittstelle „Iexample“ definiert zwei Methoden: „add“ und „subtract“. Die Klassen „Class1“ und „Class2“ implementieren diese Schnittstelle. Beachten Sie, dass diese Klassen zur Vereinfachung des Beispiels keine weiteren Aufgaben ausführen. Betrachten Sie nun das folgende Beispiel:


' 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

Die Funktion „calculate“ akzeptiert ein Element vom Typ Iexample als Parameter. Sie kann daher für diesen Parameter entweder ein Objekt vom Typ class1 oder class2 entgegennehmen. Dies geschieht in der Prozedur „Main“ mit folgenden Ergebnissen:

17
21
127
201

Wir sehen also, dass diese Eigenschaft dem Polymorphismus ähnelt, den wir im Zusammenhang mit Klassen besprochen haben. Wenn eine Gruppe von Klassen Ci, die nicht durch Vererbung miteinander verbunden sind (und daher keinen auf Vererbung basierenden Polymorphismus nutzen können), eine Reihe von Methoden mit derselben Signatur gemeinsam hat, kann es sinnvoll sein, diese Methoden in einer Schnittstelle I zusammenzufassen, von der alle relevanten Klassen erben würden. Instanzen dieser Klassen Ci können dann als Parameter für Funktionen verwendet werden, die einen Parameter vom Typ I akzeptieren, d. h. Funktionen, die nur die in der Schnittstelle I definierten Methoden der Ci-Objekte nutzen und nicht die spezifischen Attribute und Methoden der verschiedenen Ci-Klassen. Beachten Sie schließlich, dass Schnittstellenvererbung mehrfach sein kann, d. h. wir können schreiben


Public Class classe
    Implements I1,I2,...

wobei die Ij Schnittstellen sind.

3.6. Namensräume

Um eine Zeile auf den Bildschirm zu schreiben, verwenden wir die Anweisung

Console.Out.WriteLine(...)

Wenn wir uns die Definition der Console-Klasse ansehen


Namespace: System
Assembly: Mscorlib (in Mscorlib.dll)

sehen wir, dass sie Teil des System-Namespace ist. Das bedeutet, dass die Console-Klasse als System.Console bezeichnet werden sollte, und wir sollten eigentlich schreiben:

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

Wir vermeiden dies durch die Verwendung einer Import-Klausel:

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

Wir sagen, dass wir den System-Namespace mithilfe der imports-Klausel importieren. Wenn der Compiler auf einen Klassennamen stößt (hier: Console), sucht er diesen in den verschiedenen Namespaces, die durch die imports-Klauseln importiert wurden. Hier findet er die Console-Klasse im System-Namespace. Beachten wir nun die zweite Information, die mit der Console-Klasse verbunden ist:


Assembly: Mscorlib (in Mscorlib.dll)

Diese Zeile gibt an, in welcher „Assembly“ sich die Definition der Klasse „Console“ befindet. Wenn Sie außerhalb von Visual Studio.NET kompilieren und die Verweise auf die verschiedenen DLLs angeben müssen, die die von Ihnen benötigten Klassen enthalten, kann diese Information nützlich sein. Denken Sie daran, dass Sie zum Verweisen auf die zum Kompilieren einer Klasse erforderlichen DLLs Folgendes schreiben:

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

Beim Erstellen einer Klasse können Sie diese innerhalb eines Namespace erstellen. Der Zweck dieser Namespaces besteht darin, Namenskonflikte zwischen Klassen zu vermeiden, beispielsweise wenn diese verkauft werden. Stellen Sie sich zwei Unternehmen vor, E1 und E2, die Klassen vertreiben, die jeweils in den DLLs E1.dll und E2.dll verpackt sind. Angenommen, ein Kunde C erwirbt diese beiden Klassensätze, in denen beide Unternehmen eine Person-Klasse definiert haben. Kunde C kompiliert ein Programm wie folgt:

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

Wenn die Quelldatei „prog.vb“ die Klasse „Person“ verwendet, weiß der Compiler nicht, ob er die Klasse „Person“ aus der DLL „E1.dll“ oder aus der DLL „E2.dll“ verwenden soll. Er gibt einen Fehler aus. Wenn Unternehmen E1 darauf achtet, seine Klassen in einem Namespace namens E1 und Unternehmen E2 in einem Namespace namens E2 zu erstellen, werden die beiden Person-Klassen dann E1.Person und E2.Person heißen. Der Client muss in seinen Klassen entweder E1.Person oder E2.Person verwenden, nicht jedoch Person. Der Namespace beseitigt diese Mehrdeutigkeit. Um eine Klasse in einem Namespace zu erstellen, schreiben Sie:


Namespace istia.st
    Public Class personne
        ' définition de la classe
...
  end Class
end Namespace

Für dieses Beispiel erstellen wir die Klasse „Person“, die wir zuvor behandelt haben, in einem Namespace. Wir wählen „istia.st“ als Namespace. Die Klasse „Person“ sieht dann wie folgt aus:


' 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

Diese Klasse wird in die Datei „person.dll“ kompiliert:

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

Verwenden wir nun die Person-Klasse in einer Testklasse:


' 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

Um das Schreiben zu vermeiden


        Dim p1 As New istia.st.personne

Wir haben den Namespace „istia.st“ mit einer „Imports“-Klausel importiert:


Imports istia.st

Nun kompilieren wir das Testprogramm:

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

Dadurch wird eine Datei „test.exe“ erstellt, die bei Ausführung die folgenden Ergebnisse liefert:

Jean,Dupont,30

3.7. Beispiel zur Steuerberechnung

Wir greifen die bereits im vorigen Kapitel behandelte Steuerberechnung wieder auf und bearbeiten sie mithilfe einer Klasse. Sehen wir uns das Problem noch einmal an:

Wir betrachten den vereinfachten Fall eines Steuerpflichtigen, der nur sein Gehalt anzugeben hat:

  • Wir berechnen die Anzahl der Steuerklassen für den Arbeitnehmer als nbParts = nbEnfants / 2 + 1, wenn er unverheiratet ist, und als nbEnfants / 2 + 2, wenn er verheiratet ist, wobei nbEnfants die Anzahl der Kinder ist.
  • Wenn er mindestens drei Kinder hat, erhält er einen zusätzlichen halben Anteil
  • Wir berechnen sein zu versteuerndes Einkommen R = 0,72 * S, wobei S sein Jahresgehalt ist
  • Wir berechnen ihren Familienkoeffizienten QF = R / nbParts
  • Wir berechnen ihre Steuer I. Betrachten Sie die folgende Tabelle:
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

Jede Zeile enthält 3 Felder. Um die Steuer I zu berechnen, suchen Sie die erste Zeile, in der QF <= Feld1 ist. Wenn beispielsweise QF = 23.000 ist, lautet die gefundene Zeile

    24740        0.15        2072.5

Steuer I ist dann gleich 0,15*R – 2072,5*nbParts. Wenn QF so ist, dass die Bedingung QF<=field1 nie erfüllt ist, werden die Koeffizienten aus der letzten Zeile verwendet. Hier:

    0                0.65        49062

was die Steuer I = 0,65 * R - 49062 * nbParts ergibt.

Die Klasse **impot** wird wie folgt definiert:


' 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

Ein Steuerobjekt wird mit den Daten erstellt, die zur Berechnung der Steuer eines Steuerpflichtigen benötigt werden. Dies ist der feste Teil des Objekts. Sobald dieses Objekt erstellt ist, kann seine **calculate**-Methode wiederholt aufgerufen werden, um die Steuer des Steuerpflichtigen basierend auf seinem Familienstand (verheiratet oder nicht), der Anzahl der Kinder und dem Jahresgehalt zu berechnen. Ein Testprogramm könnte wie folgt aussehen:


' 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

Hier ist ein Beispiel für die Anwendung des vorherigen Programms:

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 :