Skip to content

3. Klasy, struktury, interfejsy

3.1. Obiekt na przykładzie

3.1.1. Informacje ogólne

Teraz zajmiemy się programowaniem obiektowym na przykładach. Obiekt to jednostka zawierająca dane określające jego stan (nazywane właściwościami) oraz funkcje (nazywane metodami). Obiekt jest tworzony na podstawie wzorca zwanego klasą:

Public Class c1
     ' atrybuty
    Private p1 As type1
    Private p2 As type2
....

     ' metoda
    Public Sub m1(....)
...
    End Sub    

     ' metoda
    Public Function m2(...)
....
    End Function    
End Class

Na podstawie poprzedniej klasy C1 można utworzyć wiele obiektów O1, O2,… Wszystkie będą miały właściwości p1, p2,… oraz metody m3, m4,… Jednak będą miały różne wartości dla swoich właściwości pi, dzięki czemu każda z nich będzie miała swój własny stan. Analogicznie deklaracja

    dim i, j as integer

tworzy dwa obiekty (termin ten jest tu niepoprawny) typu (klasy) Integer. Ich jedyną właściwością jest ich wartość. Jeśli O1 jest obiektem typu C1, O1.p1 oznacza właściwość p1 obiektu O1, a O1.m1 oznacza metodę m1 obiektu O1. Rozważmy pierwszy model obiektu: klasę personne.

3.1.2. Definicja klasy „osoba”

Definicja klasy personne będzie wyglądać następująco:


Public Class personne
    ' atrybuty
    Private prenom As String
    Private nom As String
    Private age As Integer

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

    ' metoda
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & age))
    End Sub
End Class

Mamy tu do czynienia z definicją klasy, a więc typu danych. Tworząc zmienne tego typu, będziemy nazywać je obiektami lub instancjami klas. Klasa jest zatem szablonem, na podstawie którego tworzone są obiekty. Elementy składowe lub pola klasy mogą być danymi (atrybutami), metodami (funkcjami) lub właściwościami. Właściwości to specjalne metody służące do sprawdzania lub ustalania wartości atrybutów obiektu. Pola te mogą być opatrzone jednym z trzech następujących słów kluczowych:

privé
Pole prywatne (private) jest dostępne wyłącznie dla wewnętrznych metod klasy
public
Pole publiczne (public) jest dostępne dla każdej funkcji, niezależnie od tego, czy została zdefiniowana w ramach klasy, czy nie
protégé
Pole chronione (protected) jest dostępne wyłącznie za pośrednictwem wewnętrznych metod klasy lub obiektu pochodnego (patrz dalsza część dotycząca dziedziczenia).

Zazwyczaj dane klasy są deklarowane jako prywatne, podczas gdy jej metody i właściwości są deklarowane jako publiczne. Oznacza to, że użytkownik obiektu (programista):

  • nie będzie miał bezpośredniego dostępu do danych prywatnych obiektu
  • będzie mógł wywoływać publiczne metody obiektu, a w szczególności te, które zapewniają dostęp do jego danych prywatnych.

Składnia deklaracji klasy jest następująca:


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

Kolejność deklaracji atrybutów private, protected i public jest dowolna.

3.1.3. Metoda inicjuje

Wróćmy do naszej klasy [personne] zadeklarowanej jako:


Public Class personne
    ' atrybuty
    Private prenom As String
    Private nom As String
    Private age As Integer

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

    ' metoda
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & age))
    End Sub
End Class

Jaka jest rola metody initialise? Ponieważ nom, prenom i age są danymi prywatnymi klasy personne, instrukcje:

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

są niedozwolone. Musimy zainicjować obiekt typu personne za pomocą metody publicznej. Do tego służy metoda initialise. Napiszemy:

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

Zapis p1.initialise jest poprawny, ponieważ initialise jest publicznie dostępny.

3.1.4. Operator new

Sekwencja instrukcji

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

jest nieprawidłowa. Instrukcja

dim p1 as personne

deklaruje p1 jako odwołanie do obiektu typu personne. Obiekt ten jeszcze nie istnieje, a zatem p1 nie jest zainicjowany. To tak, jakbyśmy napisali:

dim p1 as personne=nothing

gdzie za pomocą słowa kluczowego nothing wyraźnie wskazujemy, że zmienna p1 nie odwołuje się jeszcze do żadnego obiektu. Gdy następnie zapisujemy

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

wywołujemy metodę initialise obiektu, do którego odwołuje się zmienna p1. Jednak ten obiekt jeszcze nie istnieje, więc kompilator zgłosi błąd. Aby zmienna p1 odwoływała się do obiektu, należy zapisać:

dim p1 as personne=new personne()

Spowoduje to utworzenie jeszcze niezinicjalizowanego obiektu typu personne: atrybuty nom i prenom, które są odwołaniami do obiektów typu String, będą miały wartość nothing, a age – wartość 0. Ma więc miejsce inicjalizacja domyślna. Teraz, gdy p1 odwołuje się do obiektu, instrukcja inicjalizacji tego obiektu

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

jest poprawna.

3.1.5. Słowo kluczowe Me

Przyjrzyjmy się kodowi metody initialise:


    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    

Instrukcja Me.prenom=P oznacza, że atrybut prenom bieżącego obiektu (Me) otrzymuje wartość P. Słowo kluczowe Me określa obiekt bieżący: ten, w którym znajduje się wykonywana metoda. Skąd to wiemy? Przyjrzyjmy się, jak przebiega inicjalizacja obiektu, do którego odwołuje się p1 w programie wywołującym:

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

Wywoływana jest metoda initialise obiektu p1. Gdy w tej metodzie odwołuje się do obiektu Me, w rzeczywistości odwołuje się do obiektu p1. Metoda initialise mogłaby również zostać zapisana w następujący sposób:


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

Gdy metoda obiektu odwołuje się do atrybutu A tego obiektu, zapis Me.A jest domyślny. Należy go użyć jawnie w przypadku konfliktu identyfikatorów. Dotyczy to następującej instrukcji:


Me.age=age;

gdzie age oznacza atrybut bieżącego obiektu, a także parametr age przekazany do metody. Należy wówczas usunąć niejednoznaczność, określając atrybut age jako Me.age.

3.1.6. Program testowy

Oto krótki program testowy:


Public Class personne
    ' atrybuty
    Private prenom As String
    Private nom As String
    Private age As Integer

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

    ' metoda
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & age))
    End Sub
End Class

oraz uzyskane wyniki:

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. Korzystanie z pliku skompilowanych klas (assembly)

Należy zauważyć, że w poprzednim przykładzie w naszym programie testowym występują dwie klasy: klasy personne i test1. Istnieje inny sposób postępowania:

  • kompilujemy klasę „osoba” do osobnego pliku zwanego zestawem (assembly). Plik ten ma rozszerzenie .dll

  • kompilujemy klasę „test1”, odwołując się do zestawu zawierającego klasę „osoba”.

Oba pliki źródłowe wyglądają następująco:

test.vb

Module test1
    Sub Main()
        Dim p1 As New personne
        p1.initialise("Jean", "Dupont", 30)
        p1.identifie()
    End Sub
End module
personne2.vb

' opcje
Option Explicit On 
Option Strict On

' przestrzenie nazw
Imports System

Public Class personne
    ' atrybuty
    Private prenom As String
    Private nom As String
    Private age As Integer

    ' metoda
    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    'initialise

    ' metoda
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & age))
    End Sub    'identifie
End Class 'personne

Klasa personne jest kompilowana za pomocą następującej instrukcji:

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

W wyniku kompilacji powstał plik personne2.dll. To właśnie opcja kompilacji /t:library powoduje utworzenie pliku „assembly”. Teraz skompilujmy plik 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

Opcja kompilacji /r:personne2.dll informuje kompilator, że pewne klasy znajdują się w pliku personne2.dll. Gdy w pliku źródłowym test.vb kompilator napotka odwołanie do klasy personne, która nie została zadeklarowana w pliku źródłowym test.vb, będzie szukał klasy personne w plikach .dll wskazanych przez opcję /r. Znajdzie tam klasę personne w zestawie personne2.dll. W tym zestawie można było umieścić inne klasy. Aby podczas kompilacji wykorzystać kilka plików skompilowanych klas, należy wpisać:

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

Uruchomienie programu test1.exe daje następujące wyniki:

dos>test
Jean,Dupont,30

3.1.8. Inna metoda inicjuje

Rozważmy ponownie klasę personne i dodajmy do niej następującą metodę:


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

Mamy teraz dwie metody o nazwie initialise: jest to dopuszczalne, o ile przyjmują one różne parametry. Tak właśnie jest w tym przypadku. Parametrem jest teraz odwołanie P do osoby. Atrybuty osoby P są następnie przypisywane do bieżącego obiektu (Me). Warto zauważyć, że metoda initialise ma bezpośredni dostęp do atrybutów obiektu P, mimo że są one typu private. Zasada ta zawsze obowiązuje: obiekt O1 klasy C zawsze ma dostęp do atrybutów obiektów tej samej klasy C. Oto test nowej klasy personne, która została skompilowana do klasy personne.dll, jak wyjaśniono wcześniej:


' opcje
Option Explicit On 
Option Strict On

' przestrzenie nazw
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

oraz jej wyniki:

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

3.1.9. Konstruktory klasy „osoba”

Konstruktor to procedura o nazwie New, wywoływana podczas tworzenia obiektu. Zazwyczaj służy ona do jego inicjalizacji. Jeśli klasa posiada konstruktor przyjmujący n argumentów argi, deklaracja i inicjalizacja obiektu tej klasy może przebiegać w następującej formie:


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

lub


        dim objet as classe

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

Gdy klasa posiada jeden lub więcej konstruktorów, do utworzenia obiektu tej klasy należy obowiązkowo użyć jednego z nich. Jeśli klasa C nie posiada żadnego konstruktora, ma ona domyślny konstruktor bez parametrów: public New(). Atrybuty obiektu są wówczas inicjowane wartościami domyślnymi. Tak właśnie było w poprzednich programach, w których napisaliśmy:

    dim p1 as personne
    p1=new personne

Stwórzmy dwa konstruktory dla naszej klasy personne:


' opcje
Option Explicit On 
Option Strict On

' przestrzenie nazw
Imports System

' klasa osoba
Public Class personne
    ' atrybuty
    Private prenom As String
    Private nom As String
    Private age As Integer

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

    ' metody inicjalizacji obiektu
    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

    ' metoda
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & age))
    End Sub
End Class

Nasze dwa konstruktory ograniczają się do wywołania odpowiednich metod initialise. Przypominamy, że gdy w konstruktorze pojawia się na przykład zapis initialise(P), kompilator przekształca go na Me.initialise(P). W konstruktorze wywoływana jest zatem metoda initialise w celu przetworzenia obiektu, do którego odwołuje się Me, czyli obiektu bieżącego, który jest właśnie tworzony. Oto krótki program testowy:


' opcje 
Option Explicit On 
Option Strict On

' przestrzenie nazw
Imports System

' strona testowa
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

oraz uzyskane wyniki:

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

3.1.10. Odwołania do obiektów

Zawsze używamy tej samej klasy personne. Program testowy wygląda następująco:


' opcje
Option Explicit On 
Option Strict On

' przestrzenie nazw
Imports System

' strona testowa
Module test
    Sub Main()
        ' p1
        Dim p1 As New personne("Jean", "Dupont", 30)
        Console.Out.Write("p1=")
        p1.identifie()

        ' p2 odwołuje się do tego samego obiektu co p1
        Dim p2 As personne = p1
        Console.Out.Write("p2=")
        p2.identifie()

        ' p3 odwołuje się do obiektu, który będzie kopią obiektu, do którego odwołuje się p1
        Dim p3 As New personne(p1)
        Console.Out.Write("p3=")
        p3.identifie()

        ' zmieniamy stan obiektu, do którego odwołuje się p1
        p1.initialise("Micheline", "Benoît", 67)
        Console.Out.Write("p1=")
        p1.identifie()

        ' ponieważ p2 = p1, obiekt, do którego odwołuje się p2, musiał zmienić swój stan
        Console.Out.Write("p2=")
        p2.identifie()

        ' ponieważ p3 nie odwołuje się do tego samego obiektu co p1, obiekt, do którego odwołuje się p3, nie musiał ulec zmianie
        Console.Out.Write("p3=")
        p3.identifie()
    End Sub
End Module

Otrzymane wyniki są następujące:

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

Gdy zadeklaruje się zmienną p1 za pomocą

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

p1 odwołuje się do obiektu personne("Jean","Dupont",30), ale nie jest samym obiektem. W języku C można by powiedzieć, że jest to wskaźnik, c.a.d, wskazujący adres utworzonego obiektu. Jeśli następnie wpiszemy:

    p1=nothing

To nie obiekt personne("Jean","Dupont",30) zostaje zmodyfikowany, lecz zmienia się wartość odwołania p1. Obiekt personne("Jean","Dupont",30) zostanie „utracony”, jeśli nie będzie odwołany przez żadną inną zmienną.

Gdy piszemy:

dim p2 as personne=p1

inicjujemy wskaźnik p2: „wskazuje” on na ten sam obiekt (oznacza ten sam obiekt), co wskaźnik p1. Zatem jeśli zmodyfikujemy obiekt, na który „wskazuje” (lub do którego odwołuje się) wskaźnik p1, zmodyfikujemy również obiekt, do którego odwołuje się wskaźnik p2.

Gdy zapisujemy:

dim p3 as personne =new personne(p1);

tworzony jest nowy obiekt, będący kopią obiektu, do którego odwołuje się p1. Ten nowy obiekt będzie odwołany przez p3. Jeśli zmodyfikujemy obiekt, na który „wskazuje” (lub do którego odwołuje się) p1, nie wpłynie to w żaden sposób na obiekt, do którego odwołuje się p3. Potwierdzają to uzyskane wyniki.

3.1.11. Obiekty tymczasowe

W wyrażeniu można jawnie wywołać konstruktor obiektu: obiekt ten jest tworzony, ale nie mamy do niego dostępu (na przykład w celu jego modyfikacji). Ten obiekt tymczasowy jest tworzony na potrzeby obliczenia wyrażenia, a następnie porzucany. Zajmowana przez niego przestrzeń pamięci zostanie później automatycznie odzyskana przez program zwany „zbieraczem śmieci”, którego zadaniem jest odzyskiwanie przestrzeni pamięci zajmowanej przez obiekty, do których nie odwołują się już dane programu. Rozważmy następujący nowy program testowy:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System

' strona testowa
Module test
    Sub Main()
        Dim p As New personne(New personne("Jean", "Dupont", 30))
        p.identifie()
    End Sub
End Module

i zmodyfikujmy konstruktory klasy personne tak, aby wyświetlały komunikat:


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

Otrzymujemy następujące wyniki:

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

ilustrujące kolejną budowę obu obiektów tymczasowych.

3.1.12. Metody odczytu i zapisu atrybutów prywatnych

Dodajemy do klasy personne metody niezbędne do odczytu lub modyfikacji stanu atrybutów obiektów:


Imports System

Public Class personne

    ' atrybuty
    Private prenom As [String]
    Private nom As [String]
    Private age As Integer

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

    ' identyfikatory
    Public Sub identifie()
        Console.Out.WriteLine((prenom + "," + nom + "," + age))
    End Sub

    ' metody dostępu
    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

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

Testujemy nową klasę za pomocą następującego programu:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System

' strona testowa
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

i uzyskujemy następujące wyniki:

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

3.1.13. Właściwości

Istnieje jeszcze jeden sposób uzyskania dostępu do atrybutów klasy – poprzez utworzenie właściwości. Pozwalają one na manipulowanie atrybutami prywatnymi tak, jakby były one publiczne. Rozważmy następującą klasę personne, w której poprzednie metody dostępu i modyfikacji zostały zastąpione właściwościami do odczytu i zapisu:


' opcje
Option Explicit On 
Option Strict On

' przestrzenie nazw
Imports System

' klasa osoba
Public Class personne

    ' atrybuty
    Private _prenom As [String]
    Private _nom As [String]
    Private _age As Integer


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

    ' identyfikuje
    Public Sub identifie()
        Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
    End Sub

    ' właściwości
    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)
            ' czy wiek jest prawidłowy?
            If Value >= 0 Then
                _age = Value
            Else
                Throw New Exception("âge (" & Value & ") invalide")
            End If
        End Set
    End Property
End Class

Właściwość (Property) pozwala odczytać (get) lub ustawić (set) wartość atrybutu. W naszym przykładzie nazwom atrybutów dodaliśmy przedrostek _, aby nazwy właściwości odpowiadały nazwom atrybutów pierwotnych. Właściwość nie może bowiem nosić tej samej nazwy co atrybut, którym zarządza, ponieważ w takim przypadku dochodzi do konfliktu nazw w klasie. Dlatego nazwaliśmy nasze atrybuty _prenom, _nom, _age i odpowiednio zmodyfikowaliśmy konstruktory oraz metody. Następnie utworzyliśmy trzy właściwości: nom, prenom i age. Właściwość deklaruje się w następujący sposób:


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

gdzie Type musi być typem atrybutu zarządzanego przez właściwość. Może ona posiadać dwie metody o nazwach get i set. Metoda get zazwyczaj odpowiada za zwracanie wartości atrybutu, którym zarządza (może jednak zwracać coś innego – nic jej w tym nie przeszkadza). Metoda set przyjmuje parametr o nazwie value, który zazwyczaj przypisuje do zarządzanego przez siebie atrybutu. Może przy tej okazji sprawdzić poprawność otrzymanej wartości i ewentualnie zgłosić wyjątek, jeśli wartość okaże się nieprawidłowa. Tak właśnie postępuje się tutaj w przypadku wieku.

W jaki sposób wywoływane są metody get i set? Rozważmy następujący program testowy:


' opcje
Option Explicit On 
Option Strict On

' przestrzenie nazw
Imports System

' strona testowa
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

W instrukcji

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

próbujemy uzyskać wartości właściwości prenom, nom i age osoby P. Wywoływana jest wówczas metoda get tych właściwości, która zwraca wartość atrybutu, którym one zarządzają.

W instrukcji

        P.age = 56

chcemy ustawić wartość właściwości age. Wywoływana jest wówczas metoda set tej właściwości. Otrzyma ona wartość 56 w parametrze value.

Właściwość P klasy C, która definiuje wyłącznie metodę get, jest określana jako tylko do odczytu. Jeśli c jest obiektem klasy C, operacja c.P=wartość zostanie odrzucona przez kompilator.

Wykonanie powyższego programu testowego daje następujące wyniki:

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

Właściwości pozwalają nam zatem na manipulowanie atrybutami prywatnymi tak, jakby były one publiczne.

3.1.14. Metody i atrybuty klasy

Załóżmy, że chcemy policzyć liczbę obiektów [personne] utworzonych w aplikacji. Możemy samodzielnie zarządzać licznikiem, ale istnieje ryzyko, że zapomnimy o obiektach tymczasowych tworzonych tu i ówdzie. Bezpieczniejszym rozwiązaniem wydaje się umieszczenie w konstruktorach klasy [personne] instrukcji zwiększającej licznik. Problem polega na przekazaniu odwołania do tego licznika, aby konstruktor mógł go zwiększyć: należy przekazać im nowy parametr. Można również umieścić licznik w definicji klasy. Ponieważ jest to atrybut samej klasy, a nie konkretnego obiektu tej klasy, deklaruje się go inaczej, używając słowa kluczowego Shared:

    Private Shared _nbPersonnes As Long = 0

Aby do niego odwołać się, piszemy personne._nbPersonnes, aby pokazać, że jest to atrybut samej klasy personne. W tym przypadku utworzyliśmy atrybut prywatny, do którego nie będziemy mieli bezpośredniego dostępu spoza klasy. Tworzymy zatem właściwość publiczną, aby zapewnić dostęp do atrybutu klasy nbPersonnes. Aby ustawić wartość nbPersonnes, metoda get tej właściwości nie wymaga konkretnego obiektu personne: w rzeczywistości _nbPersonnes nie jest atrybutem konkretnego obiektu, lecz atrybutem całej klasy. Dlatego potrzebna jest również zadeklarowana właściwość o nazwie Shared:

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

która z zewnątrz będzie wywoływana przy użyciu składni personne.nbPersonnes. Właściwość jest zadeklarowana jako tylko do odczytu (ReadOnly), ponieważ nie posiada metody set. Oto przykład. Klasa personne przyjmuje następujący wygląd:


Option Explicit On 
Option Strict On

' przestrzenie nazw
Imports System

' klasa
Public Class personne
    ' atrybuty klasy
    Private Shared _nbPersonnes As Long = 0

    ' atrybuty instancji
    Private _prenom As [String]
    Private _nom As [String]
    Private _age As Integer

    ' konstruktory
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        ' jeszcze jedna osoba
        _nbPersonnes += 1
        Me._prenom = P
        Me._nom = N
        Me._age = age
    End Sub

    Public Sub New(ByVal P As personne)
        ' jeszcze jedna osoba
        _nbPersonnes += 1
        Me._prenom = P._prenom
        Me._nom = P._nom
        Me._age = P._age
    End Sub

    ' identyfikuje
    Public Sub identifie()
        Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
    End Sub

    ' właściwość klasy
    Public Shared ReadOnly Property nbPersonnes() As Long
        Get
            Return _nbPersonnes
        End Get
    End Property

    ' właściwości instancji
    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)
            ' czy wiek jest prawidłowy?
            If Value >= 0 Then
                _age = Value
            Else
                Throw New Exception("âge (" & Value & ") invalide")
            End If
        End Set
    End Property
End Class

Z następującym programem:


' opcje
Option Explicit On 
Option Strict On

' przestrzenie nazw
Imports System

' strona testowa
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

uzyskuje się następujące wyniki:

    Nombre de personnes créées : 2

3.1.15. Przekazywanie obiektu do funkcji

Wspomnieliśmy już, że domyślnie funkcja VB.NET przekazuje parametry rzeczywiste do funkcji przez wartość: wartości parametrów rzeczywistych są kopiowane do parametrów formalnych. W przypadku obiektu nie należy dać się zwieść nadużyciu językowemu, które ma miejsce systematycznie, gdy mówi się o „obiekcie” zamiast o „odwołaniu do obiektu”. Obiektem można manipulować wyłącznie za pośrednictwem odwołania (wskaźnika) do niego. Do funkcji przekazywany jest zatem nie sam obiekt, lecz odwołanie do tego obiektu. W parametrze formalnym powielana jest więc wartość odwołania, a nie wartość samego obiektu: nie dochodzi do utworzenia nowego obiektu. Jeśli odwołanie do obiektu R1 zostanie przekazane do funkcji, zostanie ono skopiowane do odpowiedniego parametru formalnego R2. W związku z tym odniesienia R2 i R1 wskazują ten sam obiekt. Jeśli funkcja modyfikuje obiekt, na który wskazuje R2, to oczywiście modyfikuje również obiekt, do którego odnosi się R1, ponieważ jest to ten sam obiekt.

Widać to na poniższym przykładzie:


' opcje
Option Explicit On 
Option Strict On

' przestrzenie nazw
Imports System

' strona testowa
Module test
    Sub Main()
        ' osoba p1
        Dim p1 As New personne("Jean", "Dupont", 30)

        ' wyświetlanie p1
        Console.Out.Write("Paramètre effectif avant modification : ")
        p1.identifie()

        ' modyfikacja p1
        modifie(p1)

        ' wyświetlanie p1
        Console.Out.Write("Paramètre effectif après modification : ")
        p1.identifie()
    End Sub

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

Otrzymane wyniki są następujące:

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

Widać, że utworzono tylko jeden obiekt: osobę p1 z procedury Main oraz że obiekt ten został rzeczywiście zmodyfikowany przez funkcję modifie.

3.1.16. Tablica osób

Obiekt jest danymi takimi jak każde inne i w związku z tym wiele obiektów można zgromadzić w tablicy:


' opcje
Option Explicit On 
Option Strict On

' przestrzenie nazw
Imports System

' strona testowa
Module test
    Sub Main()
        ' tabela osób
        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)
        ' wyświetlanie
        Console.Out.WriteLine("----------------")
        Dim i As Integer
        For i = 0 To amis.Length - 1
            amis(i).identifie()
        Next i
    End Sub
End Module

Instrukcja Dim amis(2) As personne tworzy tablicę składającą się z 3 elementów typu personne. Te 3 elementy są tutaj zainicjowane wartościami nothing, c.a.d, co oznacza, że nie odwołują się one do żadnego obiektu. Ponownie, w wyniku nieprecyzyjnego sformułowania, mówi się o tablicy obiektów, podczas gdy jest to jedynie tablica odwołań do obiektów. Utworzenie tablicy obiektów, która sama w sobie jest obiektem, nie powoduje utworzenia żadnego obiektu typu jej elementów: należy to zrobić później. Otrzymujemy następujące wyniki:

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

3.2. Dziedziczenie na przykładzie

3.2.1. Informacje ogólne

W tym miejscu omówimy pojęcie dziedziczenia. Celem dziedziczenia jest „dostosowanie” istniejącej klasy tak, aby spełniała nasze potrzeby. Załóżmy, że chcemy utworzyć klasę enseignant: nauczyciel to osoba szczególna. Posiada on atrybuty, których nie ma żadna inna osoba: na przykład przedmiot, którego naucza. Posiada jednak również atrybuty charakterystyczne dla każdej osoby: imię, nazwisko i wiek. Nauczyciel stanowi zatem pełnoprawną część klasy personne, ale posiada dodatkowe atrybuty. Zamiast tworzyć klasę enseignant od podstaw, lepiej byłoby wykorzystać istniejące elementy klasy personne i dostosować je do specyficznego charakteru nauczycieli. Umożliwia nam to koncepcja dziedziczenia. Aby wyrazić, że klasa enseignant dziedziczy właściwości klasy personne, napiszemy:


Public Class enseignant
    Inherits personne

Należy zwrócić uwagę na specyficzną składnię rozłożoną na dwa wiersze. Klasa personne nazywana jest klasą nadrzędną (lub macierzystą), a klasa enseignant – klasą pochodną (lub potomną). Obiekt enseignant posiada wszystkie cechy obiektu personne: ma te same atrybuty i metody. Atrybuty i metody klasy nadrzędnej nie są powtarzane w definicji klasy podrzędnej: wystarczy wskazać atrybuty i metody dodane przez klasę podrzędną. Zakładamy, że klasa personne jest zdefiniowana w następujący sposób:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System

' klasa osoba
Public Class personne

    ' atrybuty klasy
    Private Shared _nbPersonnes As Long = 0

    ' atrybuty instancji
    Private _prenom As [String]
    Private _nom As [String]
    Private _age As Integer


    ' konstruktory
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        ' kolejna osoba
        _nbPersonnes += 1
        ' konstrukcja
        Me._prenom = P
        Me._nom = N
        Me._age = age
        ' monitorowanie
        Console.Out.WriteLine("Construction personne(string, string, int)")
    End Sub


    Public Sub New(ByVal P As personne)
        ' jeszcze jedna osoba
        _nbPersonnes += 1
        ' budowa
        Me._prenom = P._prenom
        Me._nom = P._nom
        Me._age = P._age
        ' monitorowanie
        Console.Out.WriteLine("Construction personne(string, string, int)")
    End Sub

    ' właściwość klasy
    Public Shared ReadOnly Property nbPersonnes() As Long
        Get
            Return _nbPersonnes
        End Get
    End Property

    ' właściwości instancji
    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)
            ' czy wiek jest prawidłowy?
            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

Metoda identifie została zastąpiona przez właściwość identité, dostępną tylko do odczytu, która identyfikuje osobę. Tworzymy klasę enseignant dziedziczącą po klasie personne:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System

Public Class enseignant
    Inherits personne
    ' atrybuty
    Private _section As Integer

    ' konstruktor
    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
        ' śledzenie
        Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
    End Sub

    ' właściwość sekcji
    Public Property section() As Integer
        Get
            Return _section
        End Get
        Set(ByVal Value As Integer)
            _section = Value
        End Set
    End Property
End Class 

Klasa enseignant rozszerza metody i atrybuty klasy personne o:

  • atrybut section, który stanowi numer sekcji, do której należy nauczyciel w gronie pedagogicznym (ogólnie rzecz biorąc, jedna sekcja na jeden przedmiot)
  • nowy konstruktor umożliwiający zainicjowanie wszystkich atrybutów nauczyciela

Deklaracja


Public Class enseignant
    Inherits personne

wskazuje, że klasa enseignant wywodzi się z klasy personne.

3.2.2. Tworzenie obiektu nauczyciela

Konstruktor klasy enseignant wygląda następująco:


    ' konstruktor
    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
         ' śledzenie
        Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
    End Sub    

Oświadczenie


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

określa, że konstruktor otrzymuje cztery parametry: P, N, age, section. Musi przekazać trzy z nich – (P,N,age) – do swojej klasy bazowej, w tym przypadku klasy personne. Wiemy, że ta klasa posiada konstruktor *person(string, string, int)*, który pozwoli utworzyć obiekt typu Person na podstawie przekazanych parametrów (P,N,age). Klasa [enseignant] przekazuje parametry (P, N, wiek) do swojej klasy bazowej w następujący sposób:


        MyBase.New(P, N, age)

Po zakończeniu tworzenia klasy bazowej tworzenie obiektu enseignant jest kontynuowane poprzez wykonanie treści konstruktora:


        Me._section = section

Podsumowując, konstruktor klasy pochodnej:

  • przekazuje klasie bazowej parametry niezbędne do jej utworzenia
  • wykorzystuje pozostałe parametry do zainicjowania swoich własnych atrybutów

Można by było zapisać to w następujący sposób:


    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
        ' śledzenie
        Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
    End Sub    

To niemożliwe. Klasa personne zadeklarowała jako prywatne (private) swoje trzy pola: _prenom, _nom i _age. Tylko obiekty tej samej klasy mają bezpośredni dostęp do tych pól. Wszystkie pozostałe obiekty, w tym obiekty potomne, jak w tym przypadku, muszą korzystać z metod publicznych, aby uzyskać do nich dostęp. Sytuacja wyglądałaby inaczej, gdyby klasa personne zadeklarowała te trzy pola jako chronione (protected): wówczas zezwalałaby klasom pochodnym na bezpośredni dostęp do tych trzech pól. W naszym przykładzie użycie konstruktora klasy nadrzędnej było zatem właściwym rozwiązaniem i jest to standardowa procedura: podczas tworzenia obiektu potomnego najpierw wywołuje się konstruktor obiektu nadrzędnego, a następnie uzupełnia się inicjalizacje właściwe dla obiektu potomnego (w naszym przykładzie section).

Skompilujmy klasy personne i enseignant w zestawach:

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

Warto zauważyć, że aby skompilować klasę potomną enseignant, konieczne było odwołanie się do pliku personne.dll, który zawiera klasę personne. Spróbujmy napisać pierwszy program testowy:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System

Module test
    Sub Main()
        Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
    End Sub
End Module

Program ten ogranicza się do utworzenia obiektu enseignant (new) i zidentyfikowania go. Klasa enseignant nie posiada metody identité, ale jej klasa nadrzędna posiada taką metodę, która ponadto jest publiczna: staje się ona poprzez dziedziczenie publiczną metodą klasy enseignant. Uzyskane wyniki są następujące:

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)

Widać, że:

  • obiekt personne został utworzony przed obiektem enseignant
  • uzyskana tożsamość należy do obiektu personne

3.2.3. Przeciążenie metody lub właściwości

W poprzednim przykładzie uzyskaliśmy tożsamość części personne nauczyciela, ale brakuje pewnych informacji właściwych dla klasy enseignant (sekcja). Musimy zatem zdefiniować właściwość umożliwiającą identyfikację nauczyciela:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System

Public Class enseignant
    Inherits personne
    ' atrybuty
    Private _section As Integer

    ' konstruktor
    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
        ' śledzenie
        Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
    End Sub

    ' właściwość sekcji
    Public Property section() As Integer
        Get
            Return _section
        End Get
        Set(ByVal Value As Integer)
            _section = Value
        End Set
    End Property

    ' przeładowanie właściwości tożsamości
    Public Shadows ReadOnly Property identite() As String
        Get
            Return "enseignant(" & MyBase.identite & "," & _section & ")"
        End Get
    End Property
End Class

Metoda identite klasy enseignant opiera się na metodzie identite swojej klasy nadrzędnej (MyBase.identite) w celu wyświetlenia części „personne”, a następnie uzupełnia ją o pole _section, które jest specyficzne dla klasy enseignant. Zwróćmy uwagę na deklarację właściwości identite:


    Public Shadows ReadOnly Property identite() As String

co oznacza, że właściwość identite „maskuje” metodę o tej samej nazwie, która mogłaby istnieć w klasie nadrzędnej. Niech E będzie obiektem enseignant. Obiekt ten zawiera w sobie obiekt personne:

Właściwość „identity” jest zdefiniowana zarówno w klasie enseignant, jak i w jej klasie nadrzędnej personne. W klasie potomnej enseignant właściwość identite musi być poprzedzona słowem kluczowym „shadows”, aby wskazać, że redefiniuje się nową właściwość identite dla klasy enseignant.


    Public Shadows ReadOnly Property identite() As String

Klasa enseignant posiada teraz dwie właściwości identite:

  • jedną odziedziczoną po klasie nadrzędnej personne
  • własną

Jeśli E jest obiektem klasy enseignant, to E.identite oznacza metodę identite klasy enseignant. Mówi się, że właściwość identite klasy nadrzędnej jest „prze definiowana” przez właściwość identite klasy podrzędnej. Ogólnie rzecz biorąc, jeśli O jest obiektem, a M metodą, to w celu wykonania metody O.M system szuka metody M w następującej kolejności:

  • w klasie obiektu O
  • w jej klasie nadrzędnej, jeśli taka istnieje
  • w klasie nadrzędnej klasy nadrzędnej, jeśli taka istnieje
  • itd…

Dziedziczenie pozwala zatem na ponowne zdefiniowanie w klasie potomnej metod/właściwości o tej samej nazwie, co w klasie nadrzędnej. To właśnie umożliwia dostosowanie klasy potomnej do jej własnych potrzeb. W połączeniu z polimorfizmem, który omówimy nieco później, przeciążanie metod/właściwości stanowi główną zaletę dziedziczenia. Rozważmy ten sam przykład, co poprzednio:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System

Module test
    Sub Main()
        Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
    End Sub
End Module

Tym razem uzyskano następujące wyniki:

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

3.2.4. Polimorfizm

Rozważmy następującą sekwencję klas: C0 C1 C2 … Gdzie Ci Cj oznacza, że klasa Cj wywodzi się z klasy Ci. Oznacza to, że klasa Cj posiada wszystkie cechy klasy Ci oraz dodatkowe cechy. Niech Oi będą obiektami typu Ci. Dopuszczalne jest zapisanie:

    Oi=Oj avec j>i

W rzeczywistości, dzięki dziedziczeniu, klasa Cj posiada wszystkie cechy klasy Ci oraz dodatkowe. Zatem obiekt Oj typu Cj zawiera w sobie obiekt typu Ci. Operacja

Oi=Oj

sprawia, że Oi jest odwołaniem do obiektu typu Ci zawartego w obiekcie Oj.

Fakt, że zmienna Oi klasy Ci może w rzeczywistości odwoływać się nie tylko do obiektu klasy Ci, ale do dowolnego obiektu pochodnego od klasy Ci, nazywany jest polimorfizmem: zdolność zmiennej do odwoływania się do różnych typów obiektów. Weźmy przykład i rozważmy następującą funkcję niezależną od jakiejkolwiek klasy:


    Sub affiche(ByVal p As personne)

Równie dobrze można by zapisać

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

jak i

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

W tym ostatnim przypadku parametr formalny typu personne funkcji affiche otrzyma wartość typu enseignant. Ponieważ typ enseignant wywodzi się z typu personne, jest to dopuszczalne.

3.2.5. Przedefiniowanie i polimorfizm

Uzupełnijmy naszą procedurę affiche:


    Sub affiche(ByVal p As personne)
         ' wyświetla tożsamość p
        Console.Out.WriteLine(p.identite)
    End Sub    

Metoda p.identite zwraca ciąg znaków identyfikujący obiekt personne.. Co dzieje się w przypadku naszego poprzedniego przykładu, gdy mamy do czynienia z obiektem enseignant:


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

Przyjrzyjmy się następującemu przykładowi:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System

Module test
    Sub Main()
        ' nauczyciel
        Dim e As New enseignant("Lucile", "Dumas", 56, 61)
        affiche(e)
        ' osoba
        Dim p As New personne("Jean", "Dupont", 30)
        affiche(p)
    End Sub

    ' wyświetla
    Sub affiche(ByVal p As personne)
        ' określa tożsamość p
        Console.Out.WriteLine(p.identite)
    End Sub
End Module

Otrzymane wyniki są następujące:

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

Analiza wykonania pokazuje, że instrukcja p.identite za każdym razem wywoływała właściwość identite obiektu personne, osobę zawartą w enseignant e, a następnie samą personne p. Nie dostosowała się ona do obiektu faktycznie przekazanego jako parametr do affiche. Wolelibyśmy uzyskać pełną tożsamość obiektu enseignant e. W tym celu notacja p.identite musiałaby odnosić się do właściwości identite obiektu, na który faktycznie wskazuje p, a nie do właściwości identite części „personne” obiektu, na który faktycznie wskazuje p. Wynik ten można uzyskać, deklarując identite jako właściwość nadpisywalną (overridable) w klasie bazowej personne:


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

Słowo kluczowe „overridable” sprawia, że identite staje się właściwością nadpisywalną lub wirtualną. Słowo to można również zastosować do metod. Klasy potomne, które nadpisują właściwość lub metodę wirtualną, muszą wówczas używać słowa kluczowego overrides zamiast **shadows** do oznaczenia nadpisanej właściwości/metody. Tak więc w klasie enseignant właściwość identite jest zdefiniowana w następujący sposób:


    ' nadpisanie właściwości tożsamości
    Public Overrides ReadOnly Property identite() As String
        Get
            Return "enseignant(" & MyBase.identite & "," & _section & ")"
        End Get
    End Property

Program testowy:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System

Module test
    Sub Main()
        ' nauczyciel
        Dim e As New enseignant("Lucile", "Dumas", 56, 61)
        affiche(e)
        ' osoba
        Dim p As New personne("Jean", "Dupont", 30)
        affiche(p)
    End Sub

    ' wyświetla
    Sub affiche(ByVal p As personne)
        ' wyświetla tożsamość p
        Console.Out.WriteLine(p.identite)
    End Sub
End Module

generuje wówczas następujące wyniki:

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)

Tym razem udało nam się uzyskać pełną tożsamość nauczyciela. Zdefiniujmy teraz metodę zamiast właściwości. Klasa object jest klasą „nadrzędną” wszystkich klas VB.NET. Tak więc, gdy piszemy:

    public class personne

to domyślnie zapisujemy:

    public class personne 
         inherits object

Klasa object definiuje metodę wirtualną ToString:

Image

Metoda ToString zwraca nazwę klasy, do której należy obiekt, jak pokazano w poniższym przykładzie:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System

Module test2
    Sub Main()
        ' nauczyciel
        Console.Out.WriteLine(New enseignant("Lucile", "Dumas", 56, 61).ToString())
        ' osoba
        Console.Out.WriteLine(New personne("Jean", "Dupont", 30).ToString())
    End Sub
End Module

Wyniki są następujące:

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

Warto zauważyć, że chociaż nie zdefiniowaliśmy na nowo metody ToString w klasach personne i enseignant, można jednak zauważyć, że metoda ToString klasy object jest mimo to w stanie wyświetlić rzeczywistą nazwę klasy obiektu. Zdefiniujmy ponownie metodę ToString w klasach personne i enseignant:


    ' ToString
    Public Overrides Function ToString() As String
        ' nadajemy właściwość tożsamości
        Return identite
    End Function

Definicja jest taka sama w obu klasach. Rozważmy następujący program testowy:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System

Module test
    Sub Main()
        ' nauczyciel
        Dim e As New enseignant("Lucile", "Dumas", 56, 61)
        affiche(e)
        ' osoba
        Dim p As New personne("Jean", "Dupont", 30)
        affiche(p)
    End Sub

    ' plakat
    Sub affiche(ByVal p As personne)
        ' wyświetla tożsamość p
        Console.Out.WriteLine(p.identite)
    End Sub
End Module

Wyniki wykonania są następujące:

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. Zdefiniowanie indeksatora dla klasy

Rozważmy klasę [ArrayList] zdefiniowaną w platformie .NET. Klasa ta umożliwia przechowywanie obiektów na liście. Należy ona do przestrzeni nazw [System.Collections]. W poniższym przykładzie wykorzystujemy tę klasę do przechowywania listy osób (w szerokim znaczeniu):


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System
Imports System.Collections

' klasa listeDePersonnes
Public Class listeDePersonnes
    Inherits ArrayList

    ' dodanie osoby do listy
    Public Overloads Sub Add(ByVal p As personne)
        MyBase.Add(p)
    End Sub

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

    ' kolejny indeksator
    Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
        Get
            ' szukamy osoby o imieniu 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
        ' zwraca (element1, element2, ..., elementn)
        Dim liste As String = "("
        Dim i As Integer
        ' przeglądamy tablicę dynamiczną
        For i = 0 To (Count - 2)
            liste += "[" + Me(i).ToString + "]" + ","
        Next i        'for
        ' ostatni element
        If Count <> 0 Then
            liste += "[" + Me(i).ToString + "]"
        End If
        liste += ")"
        Return liste
    End Function
End Class

Przedstawmy kilka informacji na temat niektórych właściwości i metod klasy [ArrayList]:

Count
atrybut określający liczbę elementów listy
Add(Object)
metoda umożliwiająca dodanie obiektu do listy
Item(Integer i)
metoda zwracająca element i z listy

Zauważamy, że aby uzyskać element nr i z listy, nie wpisaliśmy [liste.Item(i)], ale bezpośrednio [liste(i)], co na pierwszy rzut oka wydaje się błędne. Jest to jednak możliwe, ponieważ klasa [ArrayList] definiuje właściwość domyślną [Item] zgodnie ze składnią podobną do poniższej:


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

Gdy kompilator napotka notację [liste(i)], sprawdza, czy klasa [ArrayList] zdefiniowała właściwość o następującej sygnaturze:


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

W tym przypadku znajdzie procedurę [Item]. Następnie przekształci zapis [liste(i)] na [liste.Item(i)]. Właściwość o nazwie [Item] nazywamy domyślną właściwością indeksowaną klasy [ArrayList]. Wykonanie powyższego programu daje następujące wyniki:

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)

Utwórzmy klasę o nazwie [listeDePersonnes], która będzie listą osób, a więc listą specjalną, którą naturalnie można wyprowadzić z klasy [ArrayList]:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System
Imports System.Collections

' klasa listeDePersonnes
Public Class listeDePersonnes
    Inherits ArrayList

    ' aby dodać osobę do listy
    Public Shadows Sub Add(ByVal p As Object)
        ' p musi być osobą
        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

    ' indeksator
    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
        ' zwraca (element1, element2, ..., elementn)
        Dim liste As String = "("
        Dim i As Integer
        ' przeglądamy tablicę dynamiczną
        For i = 0 To (Count - 2)
            liste += "[" + Me(i).ToString + "]" + ","
        Next i        'for
        ' ostatni element
        If Count <> 0 Then
            liste += "[" + Me(i).ToString + "]"
        End If
        liste += ")"
        Return liste
    End Function
End Class

Klasa posiada następujące metody i właściwości:

ToString
zwraca ciąg znaków „reprezentujący” zawartość listy
Add(personne)
metoda umożliwiająca dodanie osoby do listy
Item(Integer i)
domyślna właściwość indeksowana, zwracająca osobę o numerze i z listy

Rozważmy nowe elementy:

Utworzono nową metodę [Add].


     ' aby dodać osobę do listy
    Public Shadows Sub Add(ByVal p As Object)
        ' p musi być osobą
        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

W klasie nadrzędnej [ArrayList] istnieje już procedura o tej samej sygnaturze, stąd użycie słowa kluczowego [Shadows], które wskazuje, że nowa procedura zastępuje tę z klasy nadrzędnej. Metoda [Add] klasy potomnej sprawdza, czy dodany obiekt jest rzeczywiście typu [personne] lub pochodzi od niego za pomocą funkcji [TypeOf]. Jeśli tak nie jest, generowany jest wyjątek za pomocą instrukcji [Throw]. Jeśli dodawany obiekt jest rzeczywiście typu [personne], do dodania obiektu wykorzystywana jest metoda [Add] klasy bazowej.

Tworzona jest właściwość indeksowana:


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

W klasie bazowej istnieje już właściwość domyślna o nazwie [Item] z tą samą sygnaturą. W związku z tym konieczne jest użycie słowa kluczowego [Shadows], aby wskazać, że nowa właściwość indeksowana [Index] „ukryje” właściwość klasy bazowej [Item]. Należy zauważyć, że ma to zastosowanie nawet wtedy, gdy obie właściwości nie mają tej samej nazwy. Właściwość [Index] pozwala odwołać się do osoby nr i z listy. Opiera się ona na właściwości [Item] klasy bazowej, aby uzyskać dostęp do elementu nr i obiektu bazowego [ArrayList]. Wprowadzono zmiany typu, aby uwzględnić fakt, że właściwość [Item] obsługuje elementy typu [Object], podczas gdy właściwość [Index] obsługuje elementy typu [personne].

Na koniec nadpisujemy (Overrides) metodę [ToString] klasy [ArrayList]:


    ' toString
    Public Overrides Function ToString() As String
        ' zwraca (element1, element2, ..., elementn)
        Dim liste As String = "("
        Dim i As Integer
        ' przeglądamy tablicę dynamiczną
        For i = 0 To (Count - 2)
            liste += "[" + Me(i).ToString + "]" + ","
        Next i        'for
         ' ostatni element
        If Count <> 0 Then
            liste += "[" + Me(i).ToString + "]"
        End If
        liste += ")"
        Return liste
    End Function

Metoda ta zwraca ciąg znaków w postaci „(e1, e2, ..., en)”, gdzie e1, e2, ..., en to elementy listy. Należy zwrócić uwagę na zapis [Me(i)], który oznacza element o numerze i bieżącego obiektu [Me]. Wykorzystywana jest domyślna właściwość indeksowana. Zatem [Me(i)] jest równoważne [Me.Index(i)].

Kod klasy umieszczono w pliku [lstpersonnes2.vb] i skompilowano:

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

Tworzony jest program testowy:


' opcje
Option Explicit On 
Option Strict On

' przestrzenie nazw
Imports System
Imports System.Collections

Module test
    Sub Main()
        ' utworzenie pustej listy osób
        Dim liste As listeDePersonnes = New listeDePersonnes
        ' tworzenie osób
        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)
        ' wypełnianie listy
        liste.Add(p1)
        liste.Add(p2)
        liste.Add(e1)
        ' wyświetlanie listy
        Console.Out.WriteLine(liste.ToString)
        ' dodanie obiektu innego niż osoba
        Try
            liste.Add(4)
        Catch e As Exception
            Console.Error.WriteLine(e.Message)
        End Try
    End Sub
End Module

Zostaje skompilowany:

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

a następnie uruchomiono:

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

Można by zapisać to w następujący sposób:

dim p as personne=l("nom")

gdzie l miałoby postać [listeDePersonnes]. W tym przypadku chcemy indeksować listę l nie według numeru elementu, ale według nazwiska osoby. W tym celu definiujemy nową właściwość indeksowaną domyślnie:


    ' inny indeksator
    Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
        Get
            ' wyszukiwanie osoby o imieniu 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

Pierwszy wiersz


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

wskazuje, że ponownie tworzymy domyślną właściwość indeksowaną. Wszystkie domyślne właściwości muszą nosić tę samą nazwę, w tym przypadku [Index]. Nowa właściwość [Index] indeksuje klasę listeDePersonnes za pomocą ciągu znaków N. Wynikiem wyrażenia listeDePersonnes(N) jest liczba całkowita. Liczba ta będzie pozycją na liście osoby o nazwie N lub -1, jeśli osoba ta nie znajduje się na liście. Definiujemy jedynie właściwość get, uniemożliwiając w ten sposób zapis listeDePersonnes ("nom")=valeur, który wymagałby zdefiniowania właściwości set). Stąd słowo kluczowe [ReadOnly]. Słowo kluczowe [Shadows] jest niezbędne do ukrycia domyślnej właściwości klasy bazowej (chociaż nie ma ona tej samej sygnatury).

W treści get przeglądamy listę osób w poszukiwaniu nazwy N przekazanej jako parametr. Jeśli znajdziemy ją na pozycji i, zwracamy i, w przeciwnym razie zwracamy -1.

Nowy program testowy mógłby wyglądać następująco:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System

' strona testowa
Module test
    Sub Main()
        ' lista osób
        Dim l As New listeDePersonnes
        ' dodawanie osób
        l.Add(New personne("jean", "dumornet", 10))
        l.Add(New personne("pauline", "duchemin", 12))
        ' wyświetlanie
        Console.Out.WriteLine(("l=" + l.ToString))
        l.Add(New personne("jacques", "tartifume", 27))
        Console.Out.WriteLine(("l=" + l.ToString))
        ' zmiana elementu 1
        l(1) = New personne("sylvie", "cachan", 5)
        ' wyświetlanie elementu 1
        Console.Out.WriteLine(("l[1]=" + l(1).ToString))
        ' wyświetlanie listy l
        Console.Out.WriteLine(("l=" + l.ToString))
        ' wyszukiwanie osób
        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

Wykonanie programu daje następujące wyniki:

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

Struktura VB.NET wywodzi się bezpośrednio ze struktury języka C i jest bardzo zbliżona do klasy. Struktura jest zdefiniowana w następujący sposób:


Structure spersonne
' atrybuty
    ...
' właściwości
...
' konstruktorzy
...
' metody
End Structure

Pomimo podobieństwa w deklaracji istnieją istotne różnice między klasą a strukturą. Na przykład w strukturach nie ma pojęcia dziedziczenia. Jeśli tworzymy klasę, która nie ma być klasą pochodną, jakie różnice między strukturą a klasą pomogą nam dokonać wyboru między nimi? Posłużmy się poniższym przykładem, aby to sprawdzić:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System

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

' klasa cpersonne
Class cpersonne
    Public nom As String
    Public age As Integer
End Class

' moduł testowy
Public Module test
    Sub Main()
        ' osoba p1
        Dim sp1 As spersonne
        sp1.nom = "paul"
        sp1.age = 10
        Console.Out.WriteLine(("sp1=spersonne(" & sp1.nom & "," & sp1.age & ")"))
        ' osoba p2
        Dim sp2 As spersonne = sp1
        Console.Out.WriteLine(("sp2=spersonne(" & sp2.nom & "," & sp2.age & ")"))
        ' sp2 zostaje zmodyfikowany
        sp2.nom = "nicole"
        sp2.age = 30
        ' weryfikacja sp1 i sp2
        Console.Out.WriteLine(("sp1=cpersonne(" & sp1.nom & "," & sp1.age & ")"))
        Console.Out.WriteLine(("sp2=cpersonne(" & sp2.nom & "," & sp2.age & ")"))

        ' osoba cp1
        Dim cp1 As New cpersonne
        cp1.nom = "paul"
        cp1.age = 10
        Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
        ' osoba c P2
        Dim cp2 As cpersonne = cp1
        Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
        ' P2 został zmodyfikowany
        cp2.nom = "nicole"
        cp2.age = 30
        ' weryfikacja P1 i P2
        Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
        Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
    End Sub
End Module

Po uruchomieniu tego programu otrzymujemy następujące wyniki:

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)

Tam, gdzie na poprzednich stronach tego rozdziału używano klasy personne, teraz stosujemy strukturę spersonne:


' struktura osoby
Structure spersonne
    Public nom As String
    Public age As Integer
End Structure

Oświadczenie


        Dim sp1 As spersonne

tworzy strukturę (nazwa, wiek), a wartością sp1 jest sama ta struktura.

Deklaracja


        Dim cp1 As New cpersonne

tworzy obiekt [cpersonne] (w przybliżeniu odpowiadający naszej strukturze), a cp1 jest wówczas adresem (odwołaniem) tego obiektu.

Podsumowując

  • w przypadku struktury wartość sp1 to sama struktura
  • w przypadku klasy wartość p1 stanowi adres utworzonego obiektu

Gdy w programie zapisuje się


        Dim sp2 As spersonne = sp1

tworzona jest nowa struktura (nazwa, wiek) i inicjowana wartością p1, czyli samą strukturą.

Struktura z sp1 jest zatem powielana w sp2. Jest to kopiowanie wartości.

Instrukcja


        Dim cp2 As cpersonne = cp1

działa inaczej. Wartość cp1 jest kopiowana do cp2, ale ponieważ ta wartość jest w rzeczywistości adresem obiektu, obiekt ten nie jest powielany. Ma po prostu dwa odwołania do siebie:

W przypadku struktury zmiana wartości sp2 nie powoduje zmiany wartości sp1, co pokazuje program. W przypadku obiektu, jeśli zmodyfikujemy obiekt, na który wskazuje cp2, to obiekt, na który wskazuje cp1, również ulegnie zmianie, ponieważ jest to ten sam obiekt. Pokazują to również wyniki programu.

Z powyższych wyjaśnień wynika zatem, że:

  • wartością zmiennej typu struktura jest sama struktura
  • wartością zmiennej typu obiekt jest adres obiektu, na który wskazuje

Po zrozumieniu tej fundamentalnej różnicy struktura okazuje się bardzo zbliżona do klasy, co ilustruje poniższy przykład:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System

' struktura osoby
Structure personne
    ' atrybuty
    Private _nom As String
    Private _age As Integer

    ' właściwości
    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

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

' moduł testowy
Module test
    Sub Main()
        ' osoba p1
        Dim p1 As New personne("paul", 10)
        Console.Out.WriteLine(("p1=" & p1.ToString))
        ' osoba p2
        Dim p2 As personne = p1
        Console.Out.WriteLine(("p2=" & p2.ToString))
        ' p2 zostaje zmodyfikowany
        p2.nom = "nicole"
        p2.age = 30
        ' weryfikacja p1 i p2
        Console.Out.WriteLine(("p1=" & p1.ToString))
        Console.Out.WriteLine(("p2=" & p2.ToString))
    End Sub
End Module

Otrzymujemy następujące wyniki wykonania:

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

Jedyną zauważalną różnicą między strukturą a klasą jest to, że w przypadku klasy obiekty p1 i p2 miałyby na końcu programu tę samą wartość, czyli wartość p2.

3.5. Interfejsy

Interfejs to zbiór prototypów metod lub właściwości, które tworzą umowę. Klasa, która decyduje się na implementację interfejsu, zobowiązuje się do zapewnienia implementacji wszystkich metod zdefiniowanych w interfejsie. To kompilator weryfikuje tę implementację. Oto na przykład definicja interfejsu Istats:

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

Każda klasa implementująca ten interfejs zostanie zadeklarowana jako

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

Metody [moyenne] i [écartType] należy zdefiniować w klasie C. Rozważmy następujący kod:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System

' struktura
Public Structure élève
    Public _nom As String
    Public _note As Double

    ' konstruktor
    Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
        Me._nom = NOM
        Me._note = NOTE
    End Sub
End Structure

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

    ' producent
    Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
        ' rejestrowanie uczniów i przedmiotów
        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
        ' łączymy wszystkie oceny
        For i = 0 To (_élèves.Length - 1) - 1
            valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "],"
        Next i
        'ostatnia ocena
        If _élèves.Length <> 0 Then
            valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "]"
        End If
        valeur += ")"
        ' koniec
        Return valeur
    End Function
End Class

Klasa notes gromadzi oceny uczniów z danej klasy z danego przedmiotu:


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

Atrybuty są zadeklarowane jako protected, aby były dostępne z klasy pochodnej. Typ élève to struktura przechowująca imię i nazwisko ucznia oraz jego ocenę z przedmiotu:


Public Structure élève
    Public _nom As String
    Public _note As Double

     ' konstruktor
    Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
        Me._nom = NOM
        Me._note = NOTE
    End Sub    
End Structure

Postanawiamy wyprowadzić tę klasę notes z klasy notesStats, która posiadałaby dwa dodatkowe atrybuty: średnią i odchylenie standardowe ocen:


Public Class notesStats
    Inherits notes
    Implements Istats

     ' atrybuty
    Private _moyenne As Double
    Private _écartType As Double

Klasa notesStats implementuje następujący interfejs Istats:

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

Oznacza to, że klasa notesStats musi posiadać dwie metody o nazwach moyenne i écartType o sygnaturze określonej w interfejsie Istats. Klasa notesStats wygląda następująco:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System

Public Class notesStats
    Inherits notes
    Implements Istats

    ' atrybuty
    Private _moyenne As Double
    Private _écartType As Double

    ' konstruktor
    Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
        MyBase.New(MATIERE, ELEVES)
        ' obliczanie średniej ocen
        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
        ' odchylenie standardowe
        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

    ' metody interfejsu Istats
    Public Function moyenne() As Double Implements Istats.moyenne
        ' zwraca średnią ocen
        Return _moyenne
    End Function

    Public Function écartType() As Double Implements Istats.écartType
        ' zwraca odchylenie standardowe
        Return _écartType
    End Function
End Class

Średnia _moyenne i odchylenie standardowe _ecartType są obliczane już podczas tworzenia obiektu. Dlatego metody moyenne i écartType muszą jedynie zwracać wartości atrybutów _moyenne i _ecartType. Obie metody zwracają -1, jeśli tablica uczniów jest pusta.

Następująca klasa testowa:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System

Module test
    Sub Main()
        ' niektórzy uczniowie i oceny
        Dim ELEVES() As élève = {New élève("paul", 14), New élève("nicole", 16), New élève("jacques", 18)}
        ' które zapisujemy w obiekcie „oceny”
        Dim anglais As New notes("anglais", ELEVES)
        ' i wyświetla
        Console.Out.WriteLine((anglais.ToString))
        ' to samo z średnią i odchyleniem standardowym
        anglais = New notesStats("anglais", ELEVES)
        Console.Out.WriteLine((anglais.ToString))
    End Sub
End Module

daje następujące wyniki:

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

Klasa notesStats mogłaby równie dobrze zaimplementować metody moyenne i écartType dla siebie samej, nie wskazując, że implementuje interfejs Istats. Jaki jest sens stosowania interfejsów? Jest on następujący: funkcja może przyjmować jako parametr formalny wartość o typie interfejsu I. Każdy obiekt klasy C implementującej interfejs I może wówczas być parametrem rzeczywistym tej funkcji. Rozważmy następujący przykład:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System

' interfejs Iexemple
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

' pierwsza klasa
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

'druga klasa
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

Interfejs Iexemple definiuje dwie metody: ajouter i soustraire. Klasy classe1 i classe2 implementują ten interfejs. Należy zauważyć, że klasy te nie wykonują żadnych innych czynności, co wynika z dążenia do uproszczenia przykładu. Rozważmy teraz następujący przykład:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System

' klasa testowa
Module test

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

    ' funkcja Main
    Sub Main()
        ' utworzenie dwóch obiektów klasy1 i klasy2
        Dim c1 As New classe1
        Dim c2 As New classe2
        ' wywołania statycznej funkcji „obliczyć”
        calculer(4, 3, c1)
        calculer(14, 13, c2)
    End Sub
End Module

Funkcja calculer przyjmuje jako parametr element typu Iexemple. Może zatem otrzymać dla tego parametru zarówno obiekt typu classe1, jak i typu classe2. Tak właśnie postępuje się w procedurze Main, uzyskując następujące wyniki:

17
21
127
201

Widać zatem, że mamy tu do czynienia z właściwością zbliżoną do polimorfizmu rozpatrywanego w przypadku klas. Jeśli zbiór klas Ci, które nie są ze sobą powiązane poprzez dziedziczenie (a zatem nie można wykorzystać polimorfizmu dziedziczenia), posiada zbiór metod o tej samej sygnaturze, interesujące może być zgrupowanie tych metod w interfejsie I, z którego dziedziczyłyby wszystkie odnośne klasy. Instancje tych klas Ci mogą być wówczas wykorzystywane jako parametry funkcji przyjmujących parametr typu I, c.a.d. Są to funkcje wykorzystujące wyłącznie metody obiektów Ci zdefiniowane w interfejsie I, a nie konkretne atrybuty i metody poszczególnych klas Ci. Na koniec należy zauważyć, że dziedziczenie interfejsów może być wielokrotne, c.a.d. Można zapisać


Public Class classe
    Implements I1,I2,...

gdzie Ij to interfejsy.

3.6. Przestrzenie nazw

Aby wyświetlić wiersz na ekranie, używamy instrukcji

Console.Out.WriteLine(...)

Jeśli przyjrzymy się definicji klasy Console


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

okazuje się, że należy ona do przestrzeni nazw System. Oznacza to, że klasa Console powinna być oznaczona jako System.Console, a w rzeczywistości należałoby napisać:

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

Można tego uniknąć, stosując klauzulę imports:

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

Mówi się, że importuje się przestrzeń nazw System za pomocą klauzuli imports. Gdy kompilator napotka nazwę klasy (w tym przypadku Console), będzie jej szukał w różnych przestrzeniach nazw zaimportowanych za pomocą klauzul imports. W tym przypadku znajdzie klasę Console w przestrzeni nazw System. Zwróćmy teraz uwagę na drugą informację powiązaną z klasą Console:


Assembly: Mscorlib (in Mscorlib.dll)

Ten wiersz wskazuje, w którym „zestawie” znajduje się definicja klasy Console. Informacja ta może okazać się przydatna, gdy kompilujemy poza Visual Studio.NET i musimy podać odniesienia do różnych plików dll zawierających klasy, z których zamierzamy skorzystać. Przypominamy, że aby odwołać się do plików dll niezbędnych do kompilacji klasy, należy wpisać:

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

Tworząc klasę, można umieścić ją w przestrzeni nazw. Celem tych przestrzeni nazw jest uniknięcie konfliktów nazw między klasami, na przykład w przypadku ich sprzedaży. Rozważmy dwie firmy: E1 i E2, które dystrybuują klasy spakowane odpowiednio w plikach dll, E1.dll oraz E2.dll. Załóżmy, że klient C kupuje te dwa zestawy klas, w których obie firmy zdefiniowały klasę o nazwie personne. Klient C kompiluje program w następujący sposób:

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

Jeśli kod źródłowy prog.vb wykorzystuje klasę personne, kompilator nie będzie wiedział, czy ma pobrać klasę personne z klasy E1.dll, czy też z klasy E2.dll. Zgłosi błąd. Jeśli firma E1 zadba o utworzenie swoich klas w przestrzeni nazw o nazwie E1, a firma E2 w przestrzeni nazw o nazwie E2, obie klasy o nazwie personne będą się wówczas nazywać E1.personne i E2.personne. Klient będzie musiał używać w swoich klasach albo E1.personne, albo E2.personne, ale nie personne. Przestrzeń nazw pozwala wyeliminować tę niejednoznaczność. Aby utworzyć klasę w przestrzeni nazw, należy napisać:


Namespace istia.st
    Public Class personne
        ' definicja klasy
...
  end Class
end Namespace

Dla przykładu utwórzmy w przestrzeni nazw naszą klasę personne, którą analizowaliśmy wcześniej. Jako przestrzeń nazw wybierzemy istia.st. Klasa personne przyjmuje następującą postać:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System

' utworzenie przestrzeni nazw istia.st
Namespace istia.st
    Public Class personne
        ' atrybuty
        Private prenom As String
        Private nom As String
        Private age As Integer

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

        ' metoda
        Public Sub identifie()
            Console.Out.WriteLine((prenom & "," & nom & "," & age))
        End Sub
    End Class
End Namespace

Klasa ta jest kompilowana do pliku personne.dll:

dos>dir
11/03/2004  18:27               610 personne.vb
dos>vbc /t:library personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
12/03/2004  18:06             3 584 personne.dll
11/03/2004  18:27               610 personne.vb

Teraz wykorzystajmy klasę personne w klasie testowej:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System
Imports istia.st

' test pg
Public Module test
    Sub Main()
        Dim p1 As New personne
        p1.initialise("Jean", "Dupont", 30)
        p1.identifie()
    End Sub
End Module

Aby uniknąć wpisywania


        Dim p1 As New istia.st.personne

zaimportowaliśmy przestrzeń nazw istia.st za pomocą klauzuli imports:


Imports istia.st

Teraz skompilujmy program testowy:

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

W wyniku tego powstaje plik test.exe, którego uruchomienie daje następujące wyniki:

Jean,Dupont,30

3.7. Przykład IMPOTS

Powracamy do obliczeń podatku omówionych już w poprzednim rozdziale i przetwarzamy je przy użyciu klasy. Przypomnijmy problem:

Rozważamy uproszczony przypadek podatnika, który ma do zadeklarowania wyłącznie swoje wynagrodzenie:

  • obliczamy liczbę udziałów pracownika nbParts = nbEnfants/2 + 1, jeśli jest on osobą stanu wolnego, nbEnfants/2 + 2, jeśli jest żonaty, gdzie nbEnfants to liczba jego dzieci.
  • jeśli ma co najmniej troje dzieci, otrzymuje dodatkową połowę udziału
  • oblicza się jego dochód podlegający opodatkowaniu R = 0,72 * S, gdzie S to jego roczne wynagrodzenie
  • oblicza się jego współczynnik rodzinny QF = R / nbParts
  • oblicza się jego podatek I. Rozważmy następującą tabelę:
12620,0
0
0
13190
0,05
631
15640
0,1
1290,5
24 740
0,15
2072,5
31810
0,2
3309,5
39 970
0,25
4900
48360
0,3
6898,5
55790
0,35
9316,5
92970
0,4
12106
127860
0,45
16754,5
151250
0,50
23147,5
172 040
0,55
30710
195 000
0,60
39312
0
0,65
49062

Każdy wiersz zawiera 3 pola. Aby obliczyć podatek I, należy znaleźć pierwszy wiersz, w którym QF <= pole1. Na przykład, jeśli QF = 23000, znajdziemy wiersz

    24740        0.15        2072.5

Podatek I wynosi wówczas 0,15*R – 2072,5*nbParts. Jeśli QF jest takie, że relacja QF <= pole1 nigdy nie jest spełniona, wówczas stosuje się współczynniki z ostatniego wiersza. W tym przypadku:

    0                0.65        49062

co daje podatek I = 0,65*R – 49062*nbParts.

Klasa „impot” zostanie zdefiniowana w następujący sposób:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System

Public Class impot
    ' dane niezbędne do obliczenia podatku
    ' pochodzą ze źródła zewnętrznego
    Private limites(), coeffR(), coeffN() As Decimal

    ' producent
    Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
        ' sprawdza się, czy wszystkie trzy tabele mają ten sam rozmiar
        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
        ' w porządku
        Me.limites = LIMITES
        Me.coeffR = COEFFR
        Me.coeffN = COEFFN
    End Sub

    ' obliczenie podatku
    Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
        ' obliczenie liczby udziałów
        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
        ' obliczenie dochodu podlegającego opodatkowaniu i współczynnika rodzinnego
        Dim revenu As Decimal = 0.72D * salaire
        Dim QF As Decimal = revenu / nbParts
        ' obliczenie podatku
        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

Tworzony jest obiekt podatku zawierający dane umożliwiające obliczenie podatku podatnika. Jest to stała część obiektu. Po utworzeniu tego obiektu można wielokrotnie wywoływać jego metodę calculer, która oblicza podatek podatnika na podstawie jego stanu cywilnego (żonaty lub nie), liczby dzieci oraz rocznego wynagrodzenia. Program testowy mógłby wyglądać następująco:


' opcje
Option Strict On
Option Explicit On 

' przestrzenie nazw
Imports System
Imports Microsoft.VisualBasic

Module test
    Sub Main()
        ' interaktywny program do obliczania podatku
        ' użytkownik wprowadza trzy dane za pomocą klawiatury: żonaty nbEnfants wynagrodzenie
        ' następnie program wyświetla kwotę podatku do zapłaty
        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"

        ' tabele danych niezbędnych do obliczenia podatku
        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}

        ' utworzenie obiektu podatku
        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
        ' pętla nieskończona
        Dim marié As String
        Dim nbEnfants As Integer
        Dim salaire As Long
        While True
            ' wymagane są parametry obliczenia podatku
            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()
            ' co należy zrobić?
            If paramètres Is Nothing OrElse paramètres = "" Then
                Exit While
            End If
            ' sprawdzanie liczby argumentów w wprowadzonej linii
            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
            ' sprawdzanie poprawności parametrów
            If Not erreur Then
                ' żonaty
                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
                ' wynagrodzenie
                Try
                    salaire = Integer.Parse(args(2))
                    If salaire < 0 Then
                        Throw New Exception
                    End If
                Catch
                    erreur = True
                End Try
            End If
            ' jeśli parametry są poprawne – obliczamy podatek
            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

Oto przykładowe wykonanie powyższego programu:

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 :