Skip to content

3. الفئات، الهياكل، الواجهات

3.1. الأشياء من خلال الأمثلة

3.1.1. نظرة عامة

سنستكشف الآن البرمجة الموجهة للكائنات من خلال أمثلة. الكائن هو كيان يحتوي على بيانات تحدد حالته (تسمى الخصائص) ووظائفه (تسمى الطرق). يتم إنشاء الكائن بناءً على قالب يسمى الفئة:

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

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

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

من الفئة السابقة C1، يمكننا إنشاء العديد من الكائنات O1، O2، … وستتمتع جميعها بالخصائص p1، p2، … والطرق m3، m4، … لكن قيم خصائصها pi ستكون مختلفة، وبالتالي سيكون لكل منها حالته الخاصة. وبالمثل، فإن الإعلان

    dim i, j as integer

ينشئ كائنين (المصطلح غير صحيح هنا) من النوع (الفئة) Integer. خاصيتهما الوحيدة هي قيمتهما. إذا كان O1 كائنًا من النوع C1، فإن O1.p1 تشير إلى الخاصية p1 لـ O1 و O1.m1 إلى الطريقة m1 لـ O1. لننظر إلى نموذج كائن أول: فئة Person.

3.1.2. تعريف فئة Person

سيكون تعريف فئة Person كما يلي:


Public Class personne
    ' attributes
    Private prenom As String
    Private nom As String
    Private age As Integer
 
    ' method
    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub
 
    ' method
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & age))
    End Sub
End Class

هنا لدينا تعريف فئة، وهي نوع بيانات. عندما ننشئ متغيرات من هذا النوع، نسميها كائنات أو مثيلات فئة. وبالتالي، فإن الفئة هي قالب يتم من خلاله إنشاء الكائنات. يمكن أن تكون عناصر أو حقول الفئة بيانات (سمات) أو أساليب (دوال) أو خصائص. الخصائص هي أساليب خاصة تُستخدم لاسترداد أو تعيين قيمة سمات الكائن. يمكن أن تكون هذه الحقول مصحوبة بإحدى الكلمات الرئيسية الثلاث التالية:

خاص
لا يمكن الوصول إلى الحقل الخاص إلا من خلال الطرق الداخلية للفئة
عام
يمكن الوصول إلى الحقل العام من خلال أي دالة، سواء تم تعريفه داخل الفئة أم لا
محمي
لا يمكن الوصول إلى الحقل المحمي إلا من خلال الطرق الداخلية للفئة أو من خلال كائن مشتق (انظر مفهوم الوراثة لاحقًا).

بشكل عام، تُعلن بيانات الفئة على أنها خاصة، بينما تُعلن أساليبها وخصائصها على أنها عامة. وهذا يعني أن مستخدم الكائن (المبرمج):

  • لن يكون لديه وصول مباشر إلى البيانات الخاصة للكائن
  • يمكنه استدعاء الأساليب العامة للكائن، خاصة تلك التي تتيح الوصول إلى بياناته الخاصة.

صيغة إعلان الفئة هي كما يلي:


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

ترتيب إعلان السمات الخاصة والمحمية والعامة هو ترتيب تعسفي.

3.1.3. طريقة التهيئة

لنعد إلى فئة [person] المُعلنة على النحو التالي:


Public Class personne
    ' attributes
    Private prenom As String
    Private nom As String
    Private age As Integer
 
    ' method
    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub
 
    ' method
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & age))
    End Sub
End Class

ما هو دور طريقة initialize؟ نظرًا لأن lastName و firstName و age هي بيانات خاصة بفئة Person، فإن العبارات التالية:

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

غير صالحة. يجب علينا تهيئة كائن من نوع Person عبر طريقة عامة. وهذا هو دور طريقة initialize. سنكتب:

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

صيغة p1.initialize صحيحة لأن initialize عامة.

3.1.4. المشغل الجديد

تسلسل العبارات

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

غير صحيح. العبارة

dim p1 as personne

يعلن p1 كمرجع إلى كائن من نوع person. هذا الكائن غير موجود بعد، لذا لم يتم تهيئة p1. وكأننا نكتب:

dim p1 as personne=nothing

حيث نشير صراحةً باستخدام الكلمة الرئيسية nothing إلى أن المتغير p1 لا يشير بعد إلى أي كائن. وعندما نكتب بعد ذلك

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

فإننا نستدعي طريقة initialize للكائن الذي يشير إليه p1. ومع ذلك، فإن هذا الكائن لا يوجد بعد، وسيقوم المُترجم بالإبلاغ عن خطأ. لجعل p1 يشير إلى كائن، يجب عليك كتابة:

dim p1 as personne=new personne()

يؤدي هذا إلى إنشاء كائن Person غير مهيأ: ستكون قيمة السمتين name و first_name، اللتين تشيران إلى كائنات String، هي nothing، وستكون قيمة age هي 0. وبالتالي، هناك تهيئة افتراضية. والآن بعد أن أصبح p1 يشير إلى كائن، فإن عبارة التهيئة لهذا الكائن

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

صالحًا.

3.1.5. الكلمة الرئيسية Me

دعونا نلقي نظرة على كود طريقة *Initialize*:


    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    

تعني العبارة Me.firstName = P أن الخاصية firstName للكائن الحالي (Me) تُعيَّن لها القيمة P. تشير الكلمة الرئيسية Me إلى الكائن الحالي: الكائن الذي يتم فيه تنفيذ الأسلوب. كيف نعرف ذلك؟ لنلقِ نظرة على كيفية تهيئة الكائن المشار إليه بواسطة p1 في البرنامج المستدعي:

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

يتم استدعاء طريقة initialize الخاصة بالكائن p1. عندما تتم الإشارة إلى الكائن Me داخل هذه الطريقة، فإنها تشير في الواقع إلى الكائن p1. كان من الممكن أيضًا كتابة طريقة initialize على النحو التالي:


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

عندما تشير إحدى طرق الكائن إلى سمة A لهذا الكائن، فإن الترميز Me.A يكون ضمنيًا. ويجب استخدامه صراحةً عند وجود تعارض في المعرفات. وهذا هو الحال في العبارة التالية:


Me.age=age;

حيث تشير age إلى كل من سمة الكائن الحالي والمعلمة age التي تستقبلها الطريقة. يجب عندئذٍ حل الغموض بالإشارة إلى السمة age على أنها Me.age.

3.1.6. برنامج اختبار

فيما يلي برنامج اختبار قصير:


Public Class personne
    ' attributes
    Private prenom As String
    Private nom As String
    Private age As Integer
 
    ' method
    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub
 
    ' method
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & age))
    End Sub
End Class

والنتائج التي تم الحصول عليها:

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. استخدام ملف فئة مُجمَّع (تجميع)

لاحظ أنه في المثال السابق، هناك فئتان في برنامج الاختبار الخاص بنا: فئتا person و test1. وهناك طريقة أخرى للمتابعة:

  • قم بتجميع فئة Person في ملف منفصل يسمى تجميع. هذا الملف له امتداد .dll

  • نقوم بترجمة فئة test1 من خلال الإشارة إلى التجميع الذي يحتوي على فئة person.

يصبح الملفان المصدران كما يلي:

test.vb

Module test1
    Sub Main()
        Dim p1 As New Person
        p1.initialize("Jean", "Dupont", 30)
        p1.identify()
    End Sub
نهاية الوحدة النمطية
person2.vb

' خيارات
تشغيل الخيار Explicit
تشغيل الخيار الصارم

' مساحات الأسماء
استيراد النظام

فئة عامة Person
    ' السمات
    Private firstName As String
    الاسم الأخير الخاص كسلسلة
    عمر خاص كعدد صحيح

    ' الأسلوب
    Public Sub Initialize(ByVal FirstName As String, ByVal LastName As String, ByVal Age As Integer)
        Me.firstName = P
        Me.lastName = N
        Me.age = age
    End Sub    'التهيئة

    ' الطريقة
    Public Sub identify()
        Console.Out.WriteLine((firstName & "," & lastName & "," & age))
    End Sub    'identify
End Class 'person

يتم ترجمة فئة Person بواسطة العبارة التالية:

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

أنتجت عملية التجميع ملفًا باسم personne2.dll. خيار التجميع /t:library هو الذي يحدد إنشاء ملف "تجميع". والآن دعونا نقوم بتجميع ملف 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

يخبر خيار التجميع /r:personne2.dll المُجمِّع أنه سيجد فئات معينة في ملف personne2.dll. وعندما يجد إشارة إلى فئة Person في ملف المصدر test.vb — وهي فئة غير مُعلَّنة في ملف المصدر test.vb — سيبحث عن فئة Person في ملفات .dll المشار إليها بواسطة خيار /r. وسيجد فئة Person هنا في تجميع Person2.dll. كان بإمكاننا تضمين فئات أخرى في هذا التجميع. لاستخدام ملفات فئات متعددة تم تجميعها أثناء التجميع، سنكتب:

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

يؤدي تشغيل البرنامج test1.exe إلى النتائج التالية:

dos>test
Jean,Dupont,30

3.1.8. تقوم طريقة أخرى بتهيئة

لنواصل مع فئة Person ونضيف إليها الطريقة التالية:


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

لدينا الآن طريقتان باسم Initialize: وهذا صحيح طالما أنهما تقبلان معلمات مختلفة. وهذا هو الحال هنا. المعلمة الآن هي مرجع P إلى Person. ثم يتم تعيين سمات Person P إلى الكائن الحالي (Me). لاحظ أن طريقة initialize لها وصول مباشر إلى سمات الكائن P على الرغم من أنها من النوع private. وهذا صحيح دائمًا: الكائن O1 من الفئة C لديه دائمًا وصول إلى سمات الكائنات من نفس الفئة C. فيما يلي اختبار لفئة Person الجديدة، التي تم تجميعها في Person.dll كما هو موضح سابقًا:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
 
Module test1
    Sub Main()
        Dim p1 As New personne
        p1.initialise("Jean", "Dupont", 30)
        Console.Out.Write("p1=")
        p1.identifie()
        Dim p2 As New personne
        p2.initialise(p1)
        Console.Out.Write("p2=")
        p2.identifie()
    End Sub
End Module

ونتيجتها:

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

3.1.9. منشئات فئة Person

المنشئ هو إجراء يُسمى New يتم استدعاؤه عند إنشاء الكائن. ويُستخدم عادةً لتهيئة الكائن. إذا كانت الفئة تحتوي على منشئ يقبل n من الحجج argi، فيمكن إجراء إعلان وتهيئة كائن من تلك الفئة على النحو التالي:


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

أو


        dim objet as classe

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

عندما تحتوي فئة على منشئ واحد أو أكثر، يجب استخدام أحد هذه المنشئات لإنشاء كائن من تلك الفئة. إذا لم تحتوي الفئة C على أي منشئات، فإنها تحتوي على منشئ افتراضي، وهو المنشئ بدون معلمات: public New(). ثم يتم تهيئة سمات الكائن بالقيم الافتراضية. هذا ما حدث في البرامج السابقة، حيث كتبنا:

    dim p1 as personne
    p1=new personne

لنقم بإنشاء منشئين لفئة Person الخاصة بنا:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
 
' the person class
Public Class personne
    ' attributes
    Private prenom As String
    Private nom As String
    Private age As Integer
 
    ' manufacturers
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        initialise(P, N, age)
    End Sub
 
    Public Sub New(ByVal P As personne)
        initialise(P)
    End Sub
 
    ' object initialization methods
    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub
 
    Public Sub initialise(ByVal P As personne)
        prenom = P.prenom
        nom = P.nom
        Me.age = P.age
    End Sub
 
    ' method
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & age))
    End Sub
End Class

تقوم كلتا المنشئتين لدينا ببساطة باستدعاء طرق التهيئة المقابلة. لاحظ أنه عندما تظهر، على سبيل المثال، صيغة initialise(P) في منشئ، يقوم المُترجم بترجمتها إلى Me.initialise(P). وبالتالي، يتم استدعاء طريقة initialise في المنشئ للعمل على الكائن الذي يشير إليه Me، أي الكائن الحالي، الذي يجري إنشاؤه. وإليك برنامج اختبار قصير:


' options 
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
 
' test pg
Module test
    Sub Main()
        Dim p1 As New personne("Jean", "Dupont", 30)
        Console.Out.Write("p1=")
        p1.identifie()
        Dim p2 As New personne(p1)
        Console.Out.Write("p2=")
        p2.identifie()
    End Sub
End Module

والنتائج التي تم الحصول عليها:

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

3.1.10. مراجع الكائنات

ما زلنا نستخدم نفس فئة Person. يصبح برنامج الاختبار كما يلي:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
 
' test pg
Module test
    Sub Main()
        ' p1
        Dim p1 As New personne("Jean", "Dupont", 30)
        Console.Out.Write("p1=")
        p1.identifie()
 
        ' p2 references the same object as p1
        Dim p2 As personne = p1
        Console.Out.Write("p2=")
        p2.identifie()
 
        ' p3 references an object that will be a copy of the object referenced by p1
        Dim p3 As New personne(p1)
        Console.Out.Write("p3=")
        p3.identifie()
 
        ' change the state of the object referenced by p1
        p1.initialise("Micheline", "Benoît", 67)
        Console.Out.Write("p1=")
        p1.identifie()
 
        ' as p2=p1, the object referenced by p2 must have changed state
        Console.Out.Write("p2=")
        p2.identifie()
 
        ' as p3 does not reference the same object as p1, the object referenced by p3 must not have changed
        Console.Out.Write("p3=")
        p3.identifie()
    End Sub
End Module

النتائج التي تم الحصول عليها هي كما يلي:

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

عند تعريف المتغير p1 بـ

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

تشير p1 إلى الكائن Person("Jean", "Dupont", 30) ولكنها ليست الكائن نفسه. في لغة C، يمكننا القول إنها مؤشر، أي عنوان الكائن الذي تم إنشاؤه. إذا كتبنا بعد ذلك:

    p1=nothing

فليس الكائن person("Jean","Dupont",30) هو الذي يتم تعديله؛ بل إن مرجع p1 هو الذي يتغير قيمته. وسيُفقد الكائن person("Jean","Dupont",30) إذا لم يشار إليه بواسطة أي متغير آخر.

عندما نكتب:

dim p2 as personne=p1

نقوم بتهيئة المؤشر p2: فهو "يشير" إلى نفس الكائن (يشير إلى نفس الكائن) الذي يشير إليه المؤشر p1. وبالتالي، إذا قمنا بتعديل الكائن الذي "يشير إليه" (أو المشار إليه) p1، فإننا نقوم بتعديل الكائن المشار إليه بواسطة p2.

عندما نكتب:

dim p3 as personne =new personne(p1);

يتم إنشاء كائن جديد، وهو نسخة من الكائن الذي يشير إليه p1. وسيشير p3 إلى هذا الكائن الجديد. إذا قمت بتعديل الكائن الذي «يشير إليه» (أو يُشار إليه) p1، فإنك لا تُغير الكائن الذي يشير إليه p3 بأي شكل من الأشكال. وهذا ما تظهره النتائج.

3.1.11. الكائنات المؤقتة

في تعبير ما، يمكنك استدعاء منشئ الكائن بشكل صريح: يتم إنشاء الكائن، ولكن لا يمكنك الوصول إليه (لتعديله، على سبيل المثال). يتم إنشاء هذا الكائن المؤقت لغرض تقييم التعبير ثم يتم التخلص منه. سيتم استرداد مساحة الذاكرة التي شغلها تلقائيًا لاحقًا بواسطة برنامج يسمى "جامع القمامة"، والذي يتمثل دوره في استرداد مساحة الذاكرة التي تشغلها الكائنات التي لم تعد مرجعية لها بيانات البرنامج. ضع في اعتبارك برنامج الاختبار الجديد التالي:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
' test pg
Module test
    Sub Main()
        Dim p As New personne(New personne("Jean", "Dupont", 30))
        p.identifie()
    End Sub
End Module

ودعونا نعدل منشئات فئة Person بحيث تعرض رسالة:


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

نحصل على النتائج التالية:

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

يوضح التكوين المتتالي للكائنين المؤقتين.

3.1.12. طرق قراءة وكتابة السمات الخاصة

نضيف الطرق اللازمة إلى فئة Person لقراءة أو تعديل حالة سمات الكائنات:


Imports System
 
Public Class personne
 
    ' attributes
    Private prenom As [String]
    Private nom As [String]
    Private age As Integer

    ' manufacturers
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub
 
    Public Sub New(ByVal P As personne)
        Me.prenom = P.prenom
        Me.nom = P.nom
        Me.age = P.age
    End Sub
 
    ' identifies
    Public Sub identifie()
        Console.Out.WriteLine((prenom + "," + nom + "," + age))
    End Sub
 
    ' accessors
    Public Function getPrenom() As [String]
        Return prenom
    End Function
 
    Public Function getNom() As [String]
        Return nom
    End Function
 
    Public Function getAge() As Integer
        Return age
    End Function
 
    'modifiers
    Public Sub setPrenom(ByVal P As [String])
        Me.prenom = P
    End Sub
 
    Public Sub setNom(ByVal N As [String])
        Me.nom = N
    End Sub
 
    Public Sub setAge(ByVal age As Integer)
        Me.age = age
    End Sub
End Class

نختبر الفئة الجديدة باستخدام البرنامج التالي:


' options
Option Strict On
Option Explicit On 

' namespaces
Imports System
 
' test pg
Public Module test
    Sub Main()
        Dim P As New personne("Jean", "Michelin", 34)
        Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
        P.setAge(56)
        Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
    End Sub
End Module

ونحصل على النتائج التالية:

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

3.1.13. الخصائص

هناك طريقة أخرى للوصول إلى سمات الفئة: عن طريق إنشاء خصائص. تسمح لنا هذه الخصائص بالتعامل مع السمات الخاصة كما لو كانت عامة. انظر إلى فئة Person التالية، حيث تم استبدال أدوات الحصول والتعيين السابقة بخصائص للقراءة والكتابة:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
 
' class person
Public Class personne
 
    ' attributes
    Private _prenom As [String]
    Private _nom As [String]
    Private _age As Integer
 
 
    ' manufacturers
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        Me._prenom = P
        Me._nom = N
        Me._age = age
    End Sub
 
 
    Public Sub New(ByVal P As personne)
        Me._prenom = P._prenom
        Me._nom = P._nom
        Me._age = P._age
    End Sub
 
    ' identifies
    Public Sub identifie()
        Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
    End Sub
 
    ' properties
    Public Property prenom() As String
        Get
            Return _prenom
        End Get
        Set(ByVal Value As String)
            _prenom = Value
        End Set
    End Property
 
    Public Property nom() As String
        Get
            Return _nom
        End Get
        Set(ByVal Value As String)
            _nom = Value
        End Set
    End Property
 
    Public Property age() As Integer
        Get
            Return _age
        End Get
        Set(ByVal Value As Integer)
            ' valid age?
            If Value >= 0 Then
                _age = Value
            Else
                Throw New Exception("âge (" & Value & ") invalide")
            End If
        End Set
    End Property
End Class

تسمح لك الخاصية بقراءة (get) أو تعيين (set) قيمة السمة. في مثالنا، قمنا بوضع شرطة سفلية (_) قبل أسماء السمات بحيث تكون للخصائص نفس أسماء السمات الأولية. وذلك لأن الخاصية لا يمكن أن تحمل نفس اسم السمة التي تديرها؛ وإلا، فسيكون هناك تعارض في التسمية داخل الفئة. لذلك، قمنا بتسمية سماتنا _firstName و_lastName و_age وقمنا بتعديل المنشئات والطرق وفقًا لذلك. ثم أنشأنا ثلاث خصائص: lastName وfirstName وage. يتم إعلان الخاصية على النحو التالي:


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

حيث يجب أن يكون Type هو نوع السمة التي تديرها الخاصية. ويمكن أن تحتوي على طريقتين تسميان get و set. عادةً ما تكون طريقة get مسؤولة عن إرجاع قيمة السمة التي تديرها (ويمكنها إرجاع شيء آخر؛ فلا شيء يمنعها من القيام بذلك). تستقبل طريقة set معلمة تسمى value، والتي عادةً ما تخصصها للسمة التي تديرها. يمكنها استغلال هذه الفرصة للتحقق من صحة القيمة المستلمة، وإذا لزم الأمر، إلقاء استثناء إذا كانت القيمة غير صالحة. هذا ما يتم هنا بالنسبة للعمر.

كيف يتم استدعاء طريقتي get و set؟ انظر إلى برنامج الاختبار التالي:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
 
' test pg
Module test
    Sub Main()
        Dim P As New personne("Jean", "Michelin", 34)
        Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
        P.age = 56
        Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
        Try
            P.age = -4
        Catch ex As Exception
            Console.Error.WriteLine(ex.Message)
        End Try
    End Sub
End Module

في العبارة

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

نحاول استرداد قيم خصائص first_name و last_name و age للشخص P. يتم بعد ذلك استدعاء طريقة get لهذه الخصائص والتي تعيد قيمة السمة التي تديرها.

في العبارة

        P.age = 56

نريد تعيين قيمة الخاصية age. ثم يتم استدعاء طريقة set لهذه الخاصية. وستتلقى 56 في معلمة القيمة الخاصة بها.

يُقال إن الخاصية P لفئة C التي تُعرّف طريقة get فقط هي خاصية للقراءة فقط. إذا كان c كائنًا من فئة C، فسيتم رفض العملية c.P=value من قِبل المُترجم.

يؤدي تنفيذ برنامج الاختبار السابق إلى النتائج التالية:

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

وبالتالي، تسمح لنا الخصائص بمعالجة السمات الخاصة كما لو كانت عامة.

3.1.14. طرق وسمات الفئة

لنفترض أننا نريد حساب عدد كائنات [person] التي تم إنشاؤها في أحد التطبيقات. يمكننا إدارة العداد بأنفسنا، لكننا نخاطر بنسيان الكائنات المؤقتة التي يتم إنشاؤها هنا وهناك. يبدو أنه من الأكثر أمانًا تضمين تعليمات في منشئات فئة [person] تعمل على زيادة العداد. تكمن المشكلة في تمرير مرجع إلى هذا العداد حتى تتمكن المنشئة من زيادته: نحتاج إلى تمرير معلمة جديدة إليها. يمكننا أيضًا تضمين العداد في تعريف الفئة. نظرًا لأنه سمة للفئة نفسها وليس لكائن معين من تلك الفئة، فإننا نعلنه بشكل مختلف باستخدام الكلمة الرئيسية Shared:

    Private Shared _nbPersonnes As Long = 0

للإشارة إليها، نكتب person._nbPeople لإظهار أنها سمة تابعة لفئة Person نفسها. هنا، أنشأنا سمة خاصة لا يمكن الوصول إليها مباشرة من خارج الفئة. لذلك، نقوم بإنشاء خاصية عامة لتوفير الوصول إلى سمة الفئة nbPeople. لإرجاع قيمة nbPeople، لا تحتاج طريقة Get لهذه الخاصية إلى كائن Person محدد: في الواقع، _nbPeople ليست سمة لكائن محدد؛ بل هي سمة للفئة بأكملها. لذلك، نحتاج إلى خاصية يتم إعلانها أيضًا على أنها Shared:

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

والتي سيتم استدعاؤها من الخارج باستخدام صيغة person.nbPeople. يتم تعريف الخاصية على أنها للقراءة فقط (ReadOnly) لأنها لا توفر طريقة تعيين. إليك مثال على ذلك. تصبح فئة Person كما يلي:


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

باستخدام البرنامج التالي:


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

تم الحصول على النتائج التالية:

    Nombre de personnes créées : 2

3.1.15. تمرير كائن إلى دالة

لقد ذكرنا سابقًا أن VB.NET تقوم، بشكل افتراضي، بتمرير معلمات الدالة الفعلية حسب القيمة: حيث يتم نسخ قيم المعلمات الفعلية إلى المعلمات الشكلية. عند التعامل مع كائن، لا تنخدع بالاصطلاح اللغوي الشائع الذي يشير إلى "كائن" بدلاً من "مرجع كائن". يتم التعامل مع الكائن فقط من خلال مرجع (مؤشر) إليه. وبالتالي، فإن ما يتم تمريره إلى الدالة ليس الكائن نفسه بل مرجع إلى ذلك الكائن. وبالتالي، فإن قيمة المرجع — وليس قيمة الكائن نفسه — هي التي يتم نسخها إلى المعلمة الشكلية: ولا يتم إنشاء كائن جديد. إذا تم تمرير مرجع الكائن R1 إلى دالة، فسيتم نسخه إلى المعلمة الشكلية المقابلة R2. وبالتالي، يشير المرجعان R2 و R1 إلى نفس الكائن. إذا قامت الدالة بتعديل الكائن الذي يشير إليه R2، فمن الواضح أنها تعدل الكائن الذي يشير إليه R1 لأنهما متطابقان.

يوضح المثال التالي ذلك:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
 
' test pg
Module test
    Sub Main()
        ' one person p1
        Dim p1 As New personne("Jean", "Dupont", 30)
 
        ' p1 display
        Console.Out.Write("Paramètre effectif avant modification : ")
        p1.identifie()
 
        ' modification p1
        modifie(p1)
 
        ' p1 display
        Console.Out.Write("Paramètre effectif après modification : ")
        p1.identifie()
    End Sub

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

النتائج التي تم الحصول عليها هي كما يلي:

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

يمكننا أن نرى أنه تم إنشاء كائن واحد فقط: كائن الشخص p1 في الإجراء الرئيسي، وأن الكائن قد تم تعديله بالفعل بواسطة دالة التعديل.

3.1.16. مصفوفة من الأشخاص

الكائن هو جزء من البيانات مثل أي جزء آخر، وبالتالي، يمكن تجميع عدة كائنات في مصفوفة:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
 
' test pg
Module test
    Sub Main()
        ' a table of people
        Dim amis(2) As personne
        amis(0) = New personne("Jean", "Dupont", 30)
        amis(1) = New personne("Sylvie", "Vartan", 52)
        amis(2) = New personne("Neil", "Armstrong", 66)
        ' display
        Console.Out.WriteLine("----------------")
        Dim i As Integer
        For i = 0 To amis.Length - 1
            amis(i).identifie()
        Next i
    End Sub
End Module

يُنشئ البيان Dim friends(2) As Person مصفوفة من 3 عناصر من النوع Person. يتم تهيئة هذه العناصر الثلاثة هنا بالقيمة Nothing، مما يعني أنها لا تشير إلى أي كائنات. مرة أخرى، من الناحية الفنية، نشير إلى "مصفوفة من الكائنات" عندما تكون في الواقع مجرد مصفوفة من مراجع الكائنات. لا يؤدي إنشاء مصفوفة الكائنات، التي هي كائن بحد ذاتها، إلى إنشاء أي كائنات من نوع عناصرها: يجب القيام بذلك لاحقًا. يتم الحصول على النتائج التالية:

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

3.2. الميراث بالتمثيل

3.2.1. نظرة عامة

نناقش هنا مفهوم التوريث. الغرض من التوريث هو "تخصيص" فئة موجودة بحيث تلبي احتياجاتنا. لنفترض أننا نريد إنشاء فئة Teacher: المعلم هو نوع معين من الأشخاص. لديهم سمات لا يمتلكها الآخرون: المادة التي يدرسونها، على سبيل المثال. لكن لديهم أيضًا سمات أي شخص: الاسم الأول، واسم العائلة، والعمر. وبالتالي، فإن المعلم هو عضو كامل العضوية في فئة "Person" ولكنه يمتلك سمات إضافية. بدلاً من كتابة فئة "Teacher" من الصفر، نفضل البناء على فئة "Person" الموجودة وتكييفها مع الخصائص المحددة للمعلمين. وهذا ما يسمح لنا مفهوم التوريث بفعله. للتعبير عن أن فئة "Teacher" ترث خصائص فئة "Person"، نكتب:


Public Class enseignant
    Inherits personne

لاحظ بناء الجملة المحدد المكون من سطرين. يُطلق على فئة Person اسم الفئة الأم (أو الأساسية)، ويُطلق على فئة Teacher اسم الفئة المشتقة (أو الفرعية). يمتلك كائن Teacher جميع خصائص كائن Person: فهو يحتوي على نفس السمات والطرق. لا تتكرر هذه السمات والطرق الخاصة بالفئة الأم في تعريف الفئة الفرعية؛ بل نكتفي بتحديد السمات والطرق التي أضافتها الفئة الفرعية. نفترض أن فئة Person مُعرَّفة على النحو التالي:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
' class person
Public Class personne
 
    ' class attributes
    Private Shared _nbPersonnes As Long = 0
 
    ' instance attributes
    Private _prenom As [String]
    Private _nom As [String]
    Private _age As Integer
 
 
    ' manufacturers
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        ' one more person
        _nbPersonnes += 1
        ' construction
        Me._prenom = P
        Me._nom = N
        Me._age = age
        ' follow-up
        Console.Out.WriteLine("Construction personne(string, string, int)")
    End Sub
 
 
    Public Sub New(ByVal P As personne)
        ' one more person
        _nbPersonnes += 1
        ' construction
        Me._prenom = P._prenom
        Me._nom = P._nom
        Me._age = P._age
        ' follow-up
        Console.Out.WriteLine("Construction personne(string, string, int)")
    End Sub
 
    ' class property
    Public Shared ReadOnly Property nbPersonnes() As Long
        Get
            Return _nbPersonnes
        End Get
    End Property
 
    ' instance properties
    Public Property prenom() As String
        Get
            Return _prenom
        End Get
        Set(ByVal Value As String)
            _prenom = Value
        End Set
    End Property
 
    Public Property nom() As String
        Get
            Return _nom
        End Get
        Set(ByVal Value As String)
            _nom = Value
        End Set
    End Property
 
    Public Property age() As Integer
        Get
            Return _age
        End Get
        Set(ByVal Value As Integer)
            ' valid age?
            If Value >= 0 Then
                _age = Value
            Else
                Throw New Exception("âge (" & Value & ") invalide")
            End If
        End Set
    End Property
 
    Public ReadOnly Property identite() As String
        Get
            Return "personne(" & _prenom & "," & _nom & "," & age & ")"
        End Get
    End Property
End Class

تم استبدال الطريقة Identify بخاصية Identity للقراءة فقط، والتي تحدد هوية الشخص. نقوم بإنشاء فئة Teacher التي ترث من فئة Person:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
Public Class enseignant
    Inherits personne
    ' attributes
    Private _section As Integer
 
    ' manufacturer
    Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
        MyBase.New(P, N, age)
        Me._section = section
        ' follow-up
        Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
    End Sub
 
    ' property section
    Public Property section() As Integer
        Get
            Return _section
        End Get
        Set(ByVal Value As Integer)
            _section = Value
        End Set
    End Property
End Class 

تضيف فئة Teacher ما يلي إلى أساليب وسمات فئة Person:

  • سمة `section`، وهي رقم القسم الذي ينتمي إليه المعلم ضمن هيئة التدريس (تقريبًا قسم واحد لكل مادة)
  • منشئ جديد يقوم بتهيئة جميع سمات المعلم

الإعلان


Public Class enseignant
    Inherits personne

يشير إلى أن فئة Teacher مشتقة من فئة Person.

3.2.2. إنشاء كائن Teacher

المُنشئ لفئة Teacher هو كما يلي:


    ' manufacturer
    Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
        MyBase.New(P, N, age)
        Me._section = section
        ' follow-up
        Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
    End Sub    

الإعلان


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

يحدد أن المنشئ يتلقى أربع معلمات: P و N و age و section. ويجب عليه تمرير ثلاث منها (P و N و age) إلى فئته الأساسية، وهي في هذه الحالة فئة Person. نعلم أن هذه الفئة تحتوي على منشئ Person(string, string, int) الذي سيسمح لنا بإنشاء كائن Person باستخدام المعلمات التي تم تمريرها (P، N، age). تقوم فئة [Teacher] بتمرير المعلمات (P، N، age) إلى فئتها الأساسية على النحو التالي:


        MyBase.New(P, N, age)

بمجرد إنشاء الفئة الأساسية، يستمر إنشاء كائن Teacher بتنفيذ نص المنشئ:


        Me._section = section

باختصار، يقوم منشئ الفئة المشتقة بما يلي:

  • يمرر إلى فئته الأساسية المعلمات التي يحتاجها لإنشاء نفسه
  • يستخدم المعلمات الأخرى لتهيئة سماته الخاصة

كان بإمكاننا اختيار كتابة:


    Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
        Me._prenom = P
        Me._nom = N
        Me._age = age
        Me._section = section
        ' follow-up
        Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
    End Sub    

هذا مستحيل. فقد أعلنت فئة Person أن حقولها الثلاثة — _firstName و _lastName و _ageخاصة. ولا يمكن الوصول المباشر إلى هذه الحقول إلا للكائنات من نفس الفئة. أما جميع الكائنات الأخرى، بما في ذلك الكائنات الفرعية كما في هذه الحالة، فيجب أن تستخدم طرق عامة للوصول إليها. وكان الأمر سيختلف لو أن فئة Person أعلنت أن الحقول الثلاثة محمية: فقد كان ذلك سيسمح للفئات المشتقة بالوصول المباشر إلى الحقول الثلاثة. في مثالنا، كان استخدام منشئ الفئة الأصلية هو الحل الصحيح والنهج القياسي: عند إنشاء كائن فرعي، نستدعي أولاً منشئ الكائن الأصلي ثم نكمل عمليات التهيئة الخاصة بالكائن الفرعي (القسم في مثالنا).

دعونا نجمع فئتي Person و Teacher في مجموعات:

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

لاحظ أنه لترجمة الفئة الفرعية teacher، كان علينا الإشارة إلى ملف person.dll، الذي يحتوي على فئة person. دعونا نجرب برنامج اختبار أول:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
Module test
    Sub Main()
        Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
    End Sub
End Module

يقوم هذا البرنامج ببساطة بإنشاء كائن Teacher (جديد) وتحديد هويته. لا تحتوي فئة Teacher على طريقة Identity، ولكن فئتها الأم تحتوي على واحدة، وهي أيضًا عامة: من خلال الوراثة، تصبح طريقة عامة لفئة Teacher. النتائج التي تم الحصول عليها هي كما يلي:

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)

يمكننا أن نلاحظ أن:

  • تم إنشاء كائن person قبل كائن teacher
  • الهوية التي تم الحصول عليها هي هوية كائن الشخص

3.2.3. تحميل طريقة أو خاصية

في المثال السابق، كانت هوية الشخص جزءًا من هوية المعلم، ولكن بعض المعلومات الخاصة بفئة المعلم (القسم) مفقودة. لذلك نحتاج إلى كتابة خاصية تسمح لنا بتحديد هوية المعلم:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
Public Class enseignant
    Inherits personne
    ' attributes
    Private _section As Integer
 
    ' manufacturer
    Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
        MyBase.New(P, N, age)
        Me._section = section
        ' follow-up
        Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
    End Sub
 
    ' property section
    Public Property section() As Integer
        Get
            Return _section
        End Get
        Set(ByVal Value As Integer)
            _section = Value
        End Set
    End Property
 
    ' overload property identity
    Public Shadows ReadOnly Property identite() As String
        Get
            Return "enseignant(" & MyBase.identite & "," & _section & ")"
        End Get
    End Property
End Class

تعتمد طريقة identity لفئة Teacher على طريقة identity لفئتها الأم (MyBase.identity) لعرض جزء "person" الخاص بها، ثم تكملها بحقل _section، وهو خاص بفئة Teacher. لاحظ إعلان خاصية identity:


    Public Shadows ReadOnly Property identite() As String

وهو ما يشير إلى أن خاصية identity "تخفي" الطريقة التي تحمل الاسم نفسه والتي قد تكون موجودة في الفئة الأصلية. لنفترض وجود كائن Teacher E. يحتوي هذا الكائن على كائن Person بداخله:

يتم تعريف خاصية identity في كل من فئة Teacher وفئة Person الأصلية. في فئة Teacher الفرعية، يجب أن تسبق خاصية identity كلمة shadows للإشارة إلى أننا نعيد تعريف خاصية identity جديدة لفئة Teacher.


    Public Shadows ReadOnly Property identite() As String

أصبحت فئة Teacher الآن تحتوي على خاصيتين للهوية:

  • الواحدة الموروثة من الفئة الأم Person
  • والأخرى الخاصة بها

إذا كان E كائنًا من فئة Teacher، فإن E.identity تشير إلى طريقة identity الخاصة بفئة Teacher. نقول إن خاصية identity الخاصة بالفئة الأصلية يتم "تجاوزها" بواسطة خاصية identity الخاصة بالفئة الفرعية. بشكل عام، إذا كان O كائنًا و M طريقة، لتنفيذ الطريقة O.M، يبحث النظام عن الطريقة M بالترتيب التالي:

  • في فئة الكائن O
  • في فئتها الأم، إذا كان لها فئة أم
  • في الفئة الأم لفئتها الأم، إن وجدت
  • وما إلى ذلك…

وبالتالي، تسمح الوراثة بإعادة تعريف الطرق/الخصائص التي تحمل الاسم نفسه في الفئة الأم في الفئة الفرعية. وهذا ما يسمح بتكييف الفئة الفرعية مع احتياجاتها الخاصة. إلى جانب تعدد الأشكال، الذي سنناقشه بعد قليل، يعد تحميل الطرق/الخصائص المفرط الميزة الرئيسية للوراثة. لنأخذ نفس المثال السابق:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System

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

النتائج التي تم الحصول عليها هذه المرة هي كما يلي:

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

3.2.4. التعدد

لنفترض وجود تسلسل هرمي للفئات: C0C1C2 … Cn حيث يشير CiCj إلى أن الفئة Cj مشتقة من الفئة Ci. وهذا يعني أن الفئة Cj تمتلك جميع خصائص الفئة Ci بالإضافة إلى خصائص إضافية. لنفترض أن Oi هي كائنات من النوع Ci. من الصحيح كتابة:

    Oi=Oj avec j>i

في الواقع، من خلال الوراثة، تمتلك الفئة Cj جميع خصائص الفئة Ci بالإضافة إلى خصائص إضافية. لذلك، فإن الكائن Oj من النوع Cj يحتوي في داخله على كائن من النوع Ci. العملية

Oi=Oj

تعني أن Oi هو مرجع إلى الكائن من النوع Ci الموجود داخل الكائن Oj.

حقيقة أن المتغير Oi من الفئة Ci يمكنه في الواقع الإشارة ليس فقط إلى كائن من الفئة Ci بل إلى أي كائن مشتق من الفئة Ci تسمى تعدد الأشكال: وهي قدرة المتغير على الإشارة إلى أنواع مختلفة من الكائنات. لنأخذ مثالاً وننظر إلى الدالة التالية المستقلة عن أي فئة:


    Sub affiche(ByVal p As personne)

يمكننا بسهولة كتابة

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

as

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

في الحالة الأخيرة، ستتلقى المعلمة الشكلية من النوع «person» في دالة العرض قيمة من النوع «teacher». ونظرًا لأن النوع «teacher» مشتق من النوع «person»، فإن هذا الأمر صحيح.

3.2.5. إعادة التعريف والتعدد

دعونا نكمل إجراء العرض الخاص بنا:


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

تُرجع طريقة p.identity سلسلة تحدد هوية كائن Person. ماذا يحدث في المثال السابق عند التعامل مع كائن Teacher:


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

لنلقِ نظرة على المثال التالي:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
Module test
    Sub Main()
        ' a teacher
        Dim e As New enseignant("Lucile", "Dumas", 56, 61)
        affiche(e)
        ' a person
        Dim p As New personne("Jean", "Dupont", 30)
        affiche(p)
    End Sub
 
    ' poster
    Sub affiche(ByVal p As personne)
        ' displays identity of p
        Console.Out.WriteLine(p.identite)
    End Sub
End Module

والنتائج التي تم التوصل إليها هي كما يلي:

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

يُظهر التنفيذ أن العبارة p.identity نفذت خاصية الهوية لشخص في كل مرة: أولاً الشخص الموجود في المعلم e، ثم الشخص p نفسه. لم تتكيف مع الكائن الذي تم تمريره فعليًا كمعلمة لعرضه. كنا نفضل الحصول على الهوية الكاملة للمعلم e. لتحقيق ذلك، كان من الضروري أن تشير الترميز p.identity إلى خاصية الهوية للكائن الذي يشير إليه p فعليًا بدلاً من خاصية الهوية لجزء "person" من الكائن الذي يشير إليه p فعليًا. من الممكن تحقيق هذه النتيجة عن طريق إعلان identity كخاصية قابلة للتجاوز في الفئة الأساسية Person:


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

تجعل الكلمة الرئيسية overridable الخاصية *identite* خاصية قابلة للتحسين أو افتراضية. يمكن أيضًا تطبيق هذه الكلمة الرئيسية على الطرق. يجب على الفئات الفرعية التي تعيد تعريف خاصية أو طريقة افتراضية استخدام الكلمة الرئيسية overrides بدلاً من shadows لتأهيل الخاصية أو الطريقة المعاد تعريفها. وبالتالي، في فئة enseignant، يتم تعريف الخاصية identite على النحو التالي:


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

برنامج الاختبار:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
Module test
    Sub Main()
        ' a teacher
        Dim e As New enseignant("Lucile", "Dumas", 56, 61)
        affiche(e)
        ' a person
        Dim p As New personne("Jean", "Dupont", 30)
        affiche(p)
    End Sub
 
    ' poster
    Sub affiche(ByVal p As personne)
        ' displays identity of p
        Console.Out.WriteLine(p.identite)
    End Sub
End Module

ثم ينتج النتائج التالية:

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)

هذه المرة، نجحنا في الحصول على الهوية الكاملة للمعلم. دعونا الآن نعيد تعريف طريقة بدلاً من خاصية. فئة Object هي الفئة "الأم" لجميع فئات VB.NET. لذا عندما نكتب:

    public class personne

فإننا نكتب ضمناً:

    public class personne 
         inherits object

تُعرّف فئة object طريقة ToString افتراضية:

Image

تُرجع طريقة ToString اسم الفئة التي ينتمي إليها الكائن، كما هو موضح في المثال التالي:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
Module test2
    Sub Main()
        ' a teacher
        Console.Out.WriteLine(New enseignant("Lucile", "Dumas", 56, 61).ToString())
        ' a person
        Console.Out.WriteLine(New personne("Jean", "Dupont", 30).ToString())
    End Sub
End Module

النتائج هي كما يلي:

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

لاحظ أنه على الرغم من أننا لم نقم بتجاوز طريقة ToString في فئتي Person و Teacher، إلا أننا ما زلنا نرى أن طريقة ToString لفئة Object لا تزال قادرة على عرض الاسم الفعلي لفئة الكائن. دعونا نتجاوز طريقة ToString في فئتي Person و Teacher:


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

التعريف هو نفسه في كلا الفئتين. انظر إلى برنامج الاختبار التالي:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
Module test
    Sub Main()
        ' a teacher
        Dim e As New enseignant("Lucile", "Dumas", 56, 61)
        affiche(e)
        ' a person
        Dim p As New personne("Jean", "Dupont", 30)
        affiche(p)
    End Sub
 
    ' poster
    Sub affiche(ByVal p As personne)
        ' displays identity of p
        Console.Out.WriteLine(p.identite)
    End Sub
End Module

نتائج التنفيذ هي كما يلي:

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. تحديد مؤشر لفئة

لنأخذ فئة [ArrayList] المُعرَّفة مسبقًا في منصة .NET. تتيح لك هذه الفئة تخزين الكائنات في قائمة. وهي تنتمي إلى مساحة الاسم [System.Collections]. في المثال التالي، نستخدم هذه الفئة لتخزين قائمة بالأشخاص (بالمعنى الواسع):


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
Imports System.Collections
 
' class listeDePersonnes
Public Class listeDePersonnes
    Inherits ArrayList
 
    ' to add a person to the list
    Public Overloads Sub Add(ByVal p As personne)
        MyBase.Add(p)
    End Sub
 
    ' an indexer
    Default Public Shadows Property Item(ByVal i As Integer) As personne
        Get
            Return CType(MyBase.Item(i), personne)
        End Get
        Set(ByVal Value As personne)
            MyBase.Item(i) = Value
        End Set
    End Property
 
    ' another indexer
    Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
        Get
            ' we search for the person with name N
            Dim i As Integer
            For i = 0 To Count - 1
                If CType(Me(i), personne).nom = N Then
                    Return i
                End If
            Next i
            Return -1
        End Get
    End Property
 
    ' toString
    Public Overrides Function ToString() As String
        ' render (él1, él2, ..., éln)
        Dim liste As String = "("
        Dim i As Integer
        ' browse the dynamic table
        For i = 0 To (Count - 2)
            liste += "[" + Me(i).ToString + "]" + ","
        Next i        'for
        ' last element
        If Count <> 0 Then
            liste += "[" + Me(i).ToString + "]"
        End If
        liste += ")"
        Return liste
    End Function
End Class

دعونا نقدم بعض المعلومات حول خصائص وأساليب معينة لفئة [ArrayList]:

العدد
الخاصية التي تُرجع عدد العناصر في القائمة
Add(Object)
طريقة لإضافة كائن إلى القائمة
Item(Integer i)
طريقة تعرض العنصر رقم i من القائمة

نلاحظ أنه للحصول على العنصر i من القائمة، لم نكتب [list.Item(i)] بل كتبنا مباشرة [list(i)]، وهو ما يبدو للوهلة الأولى غير صحيح. ومع ذلك، فإن هذا ممكن لأن فئة [ArrayList] تُعرّف خاصية افتراضية [Item] بصيغة مشابهة لما يلي:


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

عندما يصادف المُترجم الترميز [list(i)]، فإنه يتحقق مما إذا كانت فئة [ArrayList] قد عرّفت خاصية بالصيغة التالية:


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

هنا، سيجد الإجراء [Item]. ثم سيقوم بترجمة الترميز [list(i)] إلى [list.Item(i)]. وتعد الخاصية [Item] هي الخاصية المفهرسة الافتراضية لفئة [ArrayList]. ويؤدي تشغيل البرنامج السابق إلى النتائج التالية:

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)

لنقم بإنشاء فئة تسمى [listOfPeople] تكون قائمة بالأشخاص، لذا فهي قائمة محددة يبدو من الطبيعي اشتقاقها من فئة [ArrayList]:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
Imports System.Collections
 
' class listeDePersonnes
Public Class listeDePersonnes
    Inherits ArrayList
 
    ' to add a person to the list
    Public Shadows Sub Add(ByVal p As Object)
        ' p must be a person
        If Not TypeOf (p) Is personne Then
            Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
        Else
            MyBase.Add(p)
        End If
    End Sub
 
    ' an indexer
    Default Public Shadows Property Index(ByVal i As Integer) As personne
        Get
            Return CType(MyBase.Item(i), personne)
        End Get
        Set(ByVal Value As personne)
            MyBase.Item(i) = Value
        End Set
    End Property
 
    ' toString
    Public Overrides Function ToString() As String
        ' render (él1, él2, ..., éln)
        Dim liste As String = "("
        Dim i As Integer
        ' browse the dynamic table
        For i = 0 To (Count - 2)
            liste += "[" + Me(i).ToString + "]" + ","
        Next i        'for
        ' last element
        If Count <> 0 Then
            liste += "[" + Me(i).ToString + "]"
        End If
        liste += ")"
        Return liste
    End Function
End Class

تحتوي الفئة على الطرق والخصائص التالية:

تحويل إلى سلسلة
تُرجع سلسلة "تمثل" محتويات القائمة
Add(person)
طريقة لإضافة شخص إلى القائمة
Item(Integer i)
خاصية مفهرسة افتراضية تعرض الشخص رقم i في القائمة

دعونا ننظر إلى الميزات الجديدة:

تم إنشاء طريقة جديدة [Add].


    ' to add a person to the list
    Public Shadows Sub Add(ByVal p As Object)
        ' p must be a person
        If Not TypeOf (p) Is personne Then
            Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
        Else
            MyBase.Add(p)
        End If
    End Sub

يوجد بالفعل إجراء واحد في الفئة الأصلية [ArrayList] بنفس التوقيع، ومن ثم يتم استخدام الكلمة الرئيسية [Shadows] للإشارة إلى أن الإجراء الجديد يحل محل الإجراء الموجود في الفئة الأصلية. تتحقق طريقة [Add] في الفئة الفرعية مما إذا كان الكائن المضاف هو بالفعل من النوع [person] أو نوع مشتق باستخدام الدالة [TypeOf]. إذا لم يكن الأمر كذلك، يتم إلقاء استثناء باستخدام عبارة [Throw]. إذا كان الكائن المضاف هو بالفعل من النوع [person]، يتم استخدام طريقة [Add] في الفئة الأساسية لإجراء الإضافة.

يتم إنشاء خاصية مفهرسة:


    ' an indexer
    Default Public Shadows Property Index(ByVal i As Integer) As personne
        Get
            Return CType(MyBase.Item(i), personne)
        End Get
        Set(ByVal Value As personne)
            MyBase.Item(i) = Value
        End Set
    End Property

توجد بالفعل خاصية افتراضية باسم [Item] في الفئة الأساسية بنفس التوقيع. ولذلك، يجب علينا استخدام الكلمة الرئيسية [Shadows] للإشارة إلى أن الخاصية المفهرسة الجديدة [Index] ستقوم «بإخفاء» خاصية الفئة الأساسية [Item]. لاحظ أن هذا ينطبق حتى لو لم تكن الخاصيتان تحملان الاسم نفسه. تسمح لك الخاصية [Index] بالإشارة إلى الشخص الموجود في الفهرس i في القائمة. وهي تعتمد على الخاصية [Item] للفئة الأساسية للوصول إلى العنصر الموجود في الفهرس i من كائن [ArrayList] الأساسي. يتم إجراء تغييرات في النوع لمراعاة حقيقة أن الخاصية [Item] تعمل مع عناصر من النوع [Object]، في حين أن الخاصية [Index] تعمل مع عناصر من النوع [Person].

أخيرًا، نقوم بتجاوز طريقة [ToString] للفئة [ArrayList]:


    ' toString
    Public Overrides Function ToString() As String
        ' render (él1, él2, ..., éln)
        Dim liste As String = "("
        Dim i As Integer
        ' browse the dynamic table
        For i = 0 To (Count - 2)
            liste += "[" + Me(i).ToString + "]" + ","
        Next i        'for
        ' last element
        If Count <> 0 Then
            liste += "[" + Me(i).ToString + "]"
        End If
        liste += ")"
        Return liste
    End Function

تُرجع هذه الطريقة سلسلة نصية بالصيغة "(e1,e2,...,en)"، حيث e1 و e2 و... و en هي عناصر القائمة. لاحظ الترميز [Me(i)]، الذي يشير إلى العنصر i من الكائن الحالي [Me]. هذه هي الخاصية المفهرسة الافتراضية المستخدمة. وبالتالي، فإن [Me(i)] تعادل [Me.Index(i)].

يتم وضع كود الفئة في الملف [lstpersonnes2.vb] وتجميعه:

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

تم إنشاء برنامج اختبار:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
Imports System.Collections
 
Module test
    Sub Main()
        ' create an empty list of people
        Dim liste As listeDePersonnes = New listeDePersonnes
        ' creating people
        Dim p1 As personne = New personne("paul", "chenou", 31)
        Dim p2 As personne = New personne("nicole", "chenou", 11)
        Dim e1 As enseignant = New enseignant("jacques", "sileau", 33, 61)
        ' list filling
        liste.Add(p1)
        liste.Add(p2)
        liste.Add(e1)
        ' list display
        Console.Out.WriteLine(liste.ToString)
        ' add an object other than person
        Try
            liste.Add(4)
        Catch e As Exception
            Console.Error.WriteLine(e.Message)
        End Try
    End Sub
End Module

تم ترجمة الكود:

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

ثم تم التنفيذ:

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

قد يرغب المرء في كتابة

dim p as personne=l("nom")

حيث يكون l من النوع [listOfPeople]. هنا، نريد فهرسة القائمة l ليس برقم العنصر بل باسم الشخص. للقيام بذلك، نحدد خاصية مفهرسة افتراضية جديدة:


    ' another indexer
    Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
        Get
            ' we search for the person with name N
            Dim i As Integer
            For i = 0 To Count - 1
                If CType(Me(i), personne).nom = N Then
                    Return i
                End If
            Next i
            Return -1
        End Get
    End Property

السطر الأول


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

يشير إلى أننا نقوم بإنشاء خاصية مفهرسة افتراضية جديدة. يجب أن تحمل جميع الخصائص الافتراضية نفس الاسم، وهو في هذه الحالة [Index]. تقوم الخاصية الجديدة [Index] بفهرسة فئة PeopleList باستخدام سلسلة N. تكون نتيجة PeopleList(N) عددًا صحيحًا. سيكون هذا العدد الصحيح هو موضع الشخص الذي يحمل الاسم N في القائمة، أو -1 إذا لم يكن هذا الشخص موجودًا في القائمة. نقوم بتعريف الخاصية get فقط، وبالتالي نمنع التعيين listOfPeople("name")=value، الذي كان سيتطلب تعريف الخاصية set. ومن هنا تأتي الكلمة الرئيسية [ReadOnly]. الكلمة الرئيسية [Shadows] ضرورية لإخفاء الخاصية الافتراضية للفئة الأساسية (على الرغم من أنها لا تحمل نفس التوقيع).

في نص طريقة get، نقوم بالتكرار عبر قائمة الأشخاص بحثًا عن الاسم N الذي تم تمريره كمعلمة. إذا وجدناه في الموضع i، فإننا نُرجع i؛ وإلا، فإننا نُرجع -1.

قد يكون برنامج الاختبار الجديد كما يلي:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System

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

يؤدي التنفيذ إلى النتائج التالية:

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. الهياكل

يُشتق هيكل VB.NET مباشرة من هيكل لغة C وهو مشابه جدًا للفئة. يُعرَّف الهيكل على النحو التالي:


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

على الرغم من أوجه التشابه في صياغتها، هناك اختلافات كبيرة بين الفئات والهياكل. على سبيل المثال، لا يوجد مفهوم الوراثة في الهياكل. إذا كنا نكتب فئة لا يُقصد منها أن تكون مشتقة من فئة أخرى، فما هي الاختلافات بين الهياكل والفئات التي ستساعدنا في الاختيار بين الاثنين؟ دعونا نستخدم المثال التالي لمعرفة ذلك:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
' spersonne structure
Structure spersonne
    Public nom As String
    Public age As Integer
End Structure
 
' class cperson
Class cpersonne
    Public nom As String
    Public age As Integer
End Class
 
' a test module
Public Module test
    Sub Main()
        ' a person p1
        Dim sp1 As spersonne
        sp1.nom = "paul"
        sp1.age = 10
        Console.Out.WriteLine(("sp1=spersonne(" & sp1.nom & "," & sp1.age & ")"))
        ' a p2 person
        Dim sp2 As spersonne = sp1
        Console.Out.WriteLine(("sp2=spersonne(" & sp2.nom & "," & sp2.age & ")"))
        ' sp2 is modified
        sp2.nom = "nicole"
        sp2.age = 30
        ' checking sp1 and sp2
        Console.Out.WriteLine(("sp1=cpersonne(" & sp1.nom & "," & sp1.age & ")"))
        Console.Out.WriteLine(("sp2=cpersonne(" & sp2.nom & "," & sp2.age & ")"))
 
        ' a cperson cp1
        Dim cp1 As New cpersonne
        cp1.nom = "paul"
        cp1.age = 10
        Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
        ' a P2 person
        Dim cp2 As cpersonne = cp1
        Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
        ' P2 is modified
        cp2.nom = "nicole"
        cp2.age = 30
        ' p1 and P2 verification
        Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
        Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
    End Sub
End Module

إذا قمنا بتشغيل هذا البرنامج، فسنحصل على النتائج التالية:

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)

في حين استخدمنا في الصفحات السابقة من هذا الفصل فئة Person، فإننا نستخدم الآن PersonStructure:


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

الإعلان


        Dim sp1 As spersonne

ينشئ بنية (name, age) وقيمة sp1 هي هذه البنية نفسها.

الإعلان


        Dim cp1 As New cpersonne

ينشئ كائنًا [cpersonne] (يعادل تقريبًا البنية الخاصة بنا)، ويكون cp1 عندئذٍ عنوان (مرجع) ذلك الكائن.

للتلخيص

  • في حالة البنية، تكون قيمة sp1 هي البنية نفسها
  • في حالة الفئة، تكون قيمة p1 هي عنوان الكائن الذي تم إنشاؤه

عندما نكتب في البرنامج


        Dim sp2 As spersonne = sp1

يتم إنشاء بنية جديدة (الاسم، العمر) وتهيئتها بقيمة p1، أي البنية نفسها.

وبذلك يتم نسخ بنية sp1 في sp2. وهذا هو نسخ القيمة.

العبارة


        Dim cp2 As cpersonne = cp1

تعمل بشكل مختلف. يتم نسخ قيمة cp1 إلى cp2، ولكن بما أن هذه القيمة هي في الواقع عنوان الكائن، فإن الكائن نفسه لا يتم نسخه. بل يوجد ببساطة مرجعان يشيران إليه:

في حالة البنية، إذا قمنا بتعديل قيمة sp2، فإننا لا نعدل قيمة sp1، كما يوضح البرنامج. في حالة الكائن، إذا قمنا بتعديل الكائن الذي يشير إليه cp2، فإن الكائن الذي يشير إليه cp1 يتم تعديله لأنهما متطابقان. وهذا ما توضحه أيضًا نتائج البرنامج.

وبالتالي يمكننا أن نستنتج من هذه التفسيرات أن:

  • قيمة المتغير من نوع البنية هي البنية نفسها
  • قيمة المتغير من نوع الكائن هي عنوان الكائن الذي يشير إليه

بمجرد فهم هذا الاختلاف الأساسي، يتبين أن البنية تشبه إلى حد كبير الفئة، كما هو موضح في المثال الجديد التالي:


' options
Option Strict On
Option Explicit On 

' namespaces
Imports System
 
' structure person
Structure personne
    ' attributes
    Private _nom As String
    Private _age As Integer
 
    ' properties
    Public Property nom() As String
        Get
            Return _nom
        End Get
        Set(ByVal Value As String)
            _nom = value
        End Set
    End Property
 
    Public Property age() As Integer
        Get
            Return _age
        End Get
        Set(ByVal Value As Integer)
            _age = value
        End Set
    End Property
 
    ' Manufacturer
    Public Sub New(ByVal NOM As String, ByVal AGE As Integer)
        _nom = NOM
        _age = AGE
    End Sub    'New
 
    ' TOSTRING
    Public Overrides Function ToString() As String
        Return "personne(" & nom & "," & age & ")"
    End Function
End Structure
 
' a test module
Module test
    Sub Main()
        ' one person p1
        Dim p1 As New personne("paul", 10)
        Console.Out.WriteLine(("p1=" & p1.ToString))
        ' one person p2
        Dim p2 As personne = p1
        Console.Out.WriteLine(("p2=" & p2.ToString))
        ' p2 is modified
        p2.nom = "nicole"
        p2.age = 30
        ' checking p1 and p2
        Console.Out.WriteLine(("p1=" & p1.ToString))
        Console.Out.WriteLine(("p2=" & p2.ToString))
    End Sub
End Module

تم الحصول على نتائج التنفيذ التالية:

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

الفرق الوحيد الملحوظ هنا بين البنية والفئة هو أنه في حالة الفئة، كان سيكون للكائنين p1 و p2 نفس القيمة في نهاية البرنامج، وهي قيمة p2.

3.5. الواجهات

الواجهة هي مجموعة من نماذج أولية للطرق أو الخصائص التي تشكل عقدًا. تلتزم الفئة التي تقرر تنفيذ واجهة بتوفير تنفيذ لجميع الطرق المحددة في الواجهة. يتحقق المُترجم من هذا التنفيذ. فيما يلي، على سبيل المثال، تعريف واجهة Istats:

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

سيتم إعلان أي فئة تنفذ هذه الواجهة على أنها

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

يجب تعريف الطريقتين [mean] و [standardDev] في الفئة C. انظر إلى الكود التالي:


' options
Option Strict On
Option Explicit On 

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

تجمع فئة notes الدرجات الخاصة بفصل دراسي في مادة ما:


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

يتم تعريف الخصائص على أنها محمية حتى يمكن الوصول إليها من فئة مشتقة. نوع الطالب هو بنية تخزن اسم الطالب ودرجته في المادة:


Public Structure élève
    Public _nom As String
    Public _note As Double
 
    ' manufacturer
    Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
        Me._nom = NOM
        Me._note = NOTE
    End Sub    
End Structure

قررنا اشتقاق فئة notes هذه إلى فئة notesStats التي ستحتوي على خاصيتين إضافيتين: المتوسط والانحراف المعياري للدرجات:


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

تنفذ فئة notesStats واجهة Istats التالية:

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

وهذا يعني أن فئة notesStats يجب أن تحتوي على طريقتين باسم average و standardDeviation مع التوقيعات المحددة في واجهة Istats. فئة notesStats هي كما يلي:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
Public Class notesStats
    Inherits notes
    Implements Istats
 
    ' attributes
    Private _moyenne As Double
    Private _écartType As Double
 
    ' manufacturer
    Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
        MyBase.New(MATIERE, ELEVES)
        ' average score calculation
        Dim somme As Double = 0
        Dim i As Integer
        For i = 0 To ELEVES.Length - 1
            somme += ELEVES(i)._note
        Next i
        If ELEVES.Length <> 0 Then
            _moyenne = somme / ELEVES.Length
        Else
            _moyenne = -1
        End If
        ' standard deviation
        Dim carrés As Double = 0
        For i = 0 To ELEVES.Length - 1
            carrés += Math.Pow(ELEVES(i)._note - _moyenne, 2)
        Next i
        If ELEVES.Length <> 0 Then
            _écartType = Math.Sqrt((carrés / ELEVES.Length))
        Else
            _écartType = -1
        End If
    End Sub
 
    ' ToString
    Public Overrides Function ToString() As String
        Return MyBase.ToString() & ",moyenne=" & _moyenne & ",écart-type=" & _écartType
    End Function    'ToString
 
    ' istats interface methods
    Public Function moyenne() As Double Implements Istats.moyenne
        ' makes the average score
        Return _moyenne
    End Function
 
    Public Function écartType() As Double Implements Istats.écartType
        ' makes the standard deviation
        Return _écartType
    End Function
End Class

يتم حساب المتوسط _mean والانحراف المعياري _standardDev عند إنشاء الكائن. لذلك، تقوم طريقتا mean و standardDev ببساطة بإرجاع قيم السمتين _mean و _standardDev. ترجع كلتا الطريقتين القيمة -1 إذا كان مصفوفة الطلاب فارغة.

فئة الاختبار التالية:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
Module test
    Sub Main()
        ' some students & notes
        Dim ELEVES() As élève = {New élève("paul", 14), New élève("nicole", 16), New élève("jacques", 18)}
        ' recorded in a notes object
        Dim anglais As New notes("anglais", ELEVES)
        ' and display
        Console.Out.WriteLine((anglais.ToString))
        ' idem with mean and standard deviation
        anglais = New notesStats("anglais", ELEVES)
        Console.Out.WriteLine((anglais.ToString))
    End Sub
End Module

يعطي النتائج:

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

كان بإمكان فئة notesStats أن تنفذ طريقتي average و standardDev بنفسها دون تحديد أنها تنفذ واجهة Istats. ما فائدة الواجهات؟ إنها كالتالي: يمكن لدالة أن تقبل كمعلمة شكلية قيمة من النوع I. عندئذ يمكن لأي كائن من فئة C ينفذ واجهة I أن يكون معلمة فعلية لتلك الدالة. انظر المثال التالي:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
' an Iexample interface
Public Interface Iexemple
    Function ajouter(ByVal i As Integer, ByVal j As Integer) As Integer
    Function soustraire(ByVal i As Integer, ByVal j As Integer) As Integer
End Interface
 
' a 1st class
Public Class classe1
    Implements Iexemple
 
    Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
        Return a + b + 10
    End Function
 
    Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
        Return a - b + 20
    End Function
End Class
 
'a 2nd class
Public Class classe2
    Implements Iexemple
 
    Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
        Return a + b + 100
    End Function
 
    Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
        Return a - b + 200
    End Function
End Class

تحدد واجهة Iexample طريقتين: add و subtract. وتقوم الفئتان Class1 و Class2 بتنفيذ هذه الواجهة. لاحظ أن هاتين الفئتين لا تقومان بأي شيء آخر، وذلك لتبسيط المثال. والآن انظر المثال التالي:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
' test class
Module test
 
    'calculate
    Sub calculer(ByVal i As Integer, ByVal j As Integer, ByVal inter As Iexemple)
        Console.Out.WriteLine(inter.ajouter(i, j))
        Console.Out.WriteLine(inter.soustraire(i, j))
    End Sub
 
    ' the Hand function
    Sub Main()
        ' creation of two objects class1 and class2
        Dim c1 As New classe1
        Dim c2 As New classe2
        ' static function calls calculate
        calculer(4, 3, c1)
        calculer(14, 13, c2)
    End Sub
End Module

تقبل دالة calculate عنصرًا من النوع Iexample كمعلمة. وبالتالي، يمكنها استلام كائن من النوع class1 أو class2 لهذه المعلمة. وهذا ما يتم في الإجراء Main مع النتائج التالية:

17
21
127
201

يمكننا أن نلاحظ إذن أن هذه الخاصية تشبه التعدد الشكلي الذي ناقشناه فيما يتعلق بالفئات. فإذا كانت مجموعة من الفئات Ci غير مرتبطة ببعضها البعض عن طريق التوريث (وبالتالي لا يمكنها استخدام التعدد الشكلي القائم على التوريث) تشترك في مجموعة من الطرق ذات التوقيع نفسه، فقد يكون من المفيد تجميع هذه الطرق في واجهة I ترث منها جميع الفئات ذات الصلة. يمكن بعد ذلك استخدام مثيلات هذه الفئات Ci كمعلمات للوظائف التي تقبل معلمة من النوع I، أي الوظائف التي تستخدم فقط أساليب كائنات Ci المحددة في الواجهة I وليس السمات والأساليب المحددة للفئات Ci المختلفة. أخيرًا، لاحظ أن وراثة الواجهة يمكن أن تكون متعددة، أي يمكننا كتابة


Public Class classe
    Implements I1,I2,...

حيث Ij هي واجهات.

3.6. مساحات الأسماء

لكتابة سطر على الشاشة، نستخدم العبارة

Console.Out.WriteLine(...)

إذا نظرنا إلى تعريف فئة Console


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

نرى أنه جزء من مساحة اسم System. وهذا يعني أنه يجب الإشارة إلى فئة Console باسم System.Console، ويجب أن نكتب في الواقع:

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

نتجنب ذلك باستخدام جملة الاستيراد:

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

نقول إننا نستورد مساحة اسم System باستخدام جملة imports. عندما يصادف المُترجم اسم فئة (هنا، Console)، سيبحث عنها في مساحات الأسماء المختلفة التي تم استيرادها بواسطة جمل imports. هنا، سيجد فئة Console في مساحة اسم System. الآن دعونا نلاحظ المعلومة الثانية المرتبطة بفئة Console:


Assembly: Mscorlib (in Mscorlib.dll)

يشير هذا السطر إلى "التجميع" الذي يوجد فيه تعريف فئة Console. عند التحويل البرمجي خارج Visual Studio.NET وتحتاج إلى تحديد المراجع إلى ملفات DLL المختلفة التي تحتوي على الفئات التي تحتاج إلى استخدامها، يمكن أن تكون هذه المعلومات مفيدة. تذكر أنه للإشارة إلى ملفات DLL المطلوبة لتحويل فئة برمجياً، تكتب:

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

عند إنشاء فئة، يمكنك إنشاؤها داخل مساحة اسم. الغرض من مساحات الأسماء هذه هو تجنب تعارض الأسماء بين الفئات، على سبيل المثال عند بيعها. لنفترض أن هناك شركتين، E1 و E2، توزعان فئات مجمعة على التوالي في ملفات DLL E1.dll و E2.dll. لنفترض أن العميل C يشتري هاتين المجموعتين من الفئات، حيث قامت كلتا الشركتين بتعريف فئة Person. يقوم العميل C بتجميع برنامج على النحو التالي:

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

إذا استخدم الملف المصدر prog.vb فئة Person، فلن يتمكن المُترجم من تحديد ما إذا كان عليه استخدام فئة Person الموجودة في ملف E1.dll أم تلك الموجودة في ملف E2.dll. وسيصدر إخطارًا بوجود خطأ. إذا حرصت الشركة E1 على إنشاء فئاتها في مساحة أسماء تسمى E1 والشركة E2 في مساحة أسماء تسمى E2، فسيتم تسمية فئتي Person بـ E1.Person و E2.Person. يجب على العميل استخدام E1.Person أو E2.Person في فئاته، وليس Person. تعمل مساحة الأسماء على حل هذا الغموض. لإنشاء فئة في مساحة أسماء، اكتب:


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

في هذا المثال، لنقم بإنشاء فئة Person التي درسناها سابقًا في مساحة اسم. سنختار istia.st كمساحة اسم. تصبح فئة Person كما يلي:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
' creation of istia.st namespace
Namespace istia.st
    Public Class personne
        ' attributes
        Private prenom As String
        Private nom As String
        Private age As Integer
 
        ' method
        Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
            Me.prenom = P
            Me.nom = N
            Me.age = age
        End Sub
 
        ' method
        Public Sub identifie()
            Console.Out.WriteLine((prenom & "," & nom & "," & age))
        End Sub
    End Class
End Namespace

يتم ترجمة هذه الفئة إلى ملف person.dll:

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

الآن دعونا نستخدم فئة Person في فئة اختبار:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
Imports istia.st
 
' pg test
Public Module test
    Sub Main()
        Dim p1 As New personne
        p1.initialise("Jean", "Dupont", 30)
        p1.identifie()
    End Sub
End Module

لتجنب الكتابة


        Dim p1 As New istia.st.personne

لقد قمنا باستيراد مساحة الاسم istia.st باستخدام جملة Imports:


Imports istia.st

الآن دعونا نقوم بتجميع برنامج الاختبار:

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

ينتج عن ذلك ملف test.exe الذي، عند تنفيذه، يعطي النتائج التالية:

Jean,Dupont,30

3.7. مثال حساب الضريبة

نعيد النظر في حساب الضريبة الذي تمت تغطيته بالفعل في الفصل السابق ونعالجه باستخدام فئة. دعونا نراجع المشكلة:

نأخذ الحالة المبسطة لمكلف ضريبي لا يملك سوى راتبه ليبلغ عنه:

  • نحسب عدد شرائح الضريبة للموظف على النحو التالي: nbParts = nbEnfants / 2 + 1 إذا كان غير متزوج، و nbEnfants / 2 + 2 إذا كان متزوجًا، حيث nbEnfants هو عدد الأطفال.
  • إذا كان لديه ثلاثة أطفال على الأقل، فإنه يحصل على نصف حصة إضافية
  • نحسب دخله الخاضع للضريبة R = 0.72 * S، حيث S هو راتبه السنوي
  • نحسب معامل الأسرة QF = R / nbParts
  • نحسب ضريبته I. انظر الجدول التالي:
12620.0
0
0
13,190
0.05
631
15,640
0.1
1,290.5
24,740
0.15
2,072.5
31,810
0.2
3,309.5
39,970
0.25
4,900
48,360
0.3
6,898.5
55,790
0.35
9,316.5
92,970
0.4
12,106
127,860
0.45
16,754.5
151,250
0.50
23,147.5
172,040
0.55
30,710
195,000
0.60
39,312
0
0.65
49,062

يحتوي كل صف على 3 حقول. لحساب الضريبة I، ابحث عن أول صف حيث QF <= الحقل 1. على سبيل المثال، إذا كان QF = 23,000، فسيكون الصف الذي تم العثور عليه هو

    24740        0.15        2072.5

الضريبة I تساوي إذن 0.15*R - 2072.5*nbParts. إذا كان QF بحيث لا تتحقق الشرط QF<=field1 أبدًا، يتم استخدام المعاملات من الصف الأخير. هنا:

    0                0.65        49062

مما يعطي الضريبة I = 0.65 * R - 49062 * nbParts.

سيتم تعريف فئة **impot** على النحو التالي:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
Public Class impot
    ' data required for tax calculation
    ' come from an external source
    Private limites(), coeffR(), coeffN() As Decimal
 
    ' manufacturer
    Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
        ' check that the 3 arrays have the same size
        Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length
        If Not OK Then
            Throw New Exception("Les 3 tableaux fournis n'ont pas la même taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
        End If
        ' it's good
        Me.limites = LIMITES
        Me.coeffR = COEFFR
        Me.coeffN = COEFFN
    End Sub
 
    ' tAX CALCULATION
    Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
        ' calculating the number of shares
        Dim nbParts As Decimal
        If marié Then
            nbParts = CDec(nbEnfants) / 2 + 2
        Else
            nbParts = CDec(nbEnfants) / 2 + 1
        End If
        If nbEnfants >= 3 Then
            nbParts += 0.5D
        End If
        ' calculation of taxable income & family quota
        Dim revenu As Decimal = 0.72D * salaire
        Dim QF As Decimal = revenu / nbParts
        ' tAX CALCULATION
        limites((limites.Length - 1)) = QF + 1
        Dim i As Integer = 0
        While QF > limites(i)
            i += 1
        End While
        Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
    End Function
End Class

يتم إنشاء كائن ضريبي بالبيانات اللازمة لحساب ضريبة المكلف. هذا هو الجزء الثابت من الكائن. بمجرد إنشاء هذا الكائن، يمكن استدعاء طريقة **calculate** الخاصة به بشكل متكرر لحساب ضريبة المكلف بناءً على حالته الاجتماعية (متزوج أم لا)، وعدد الأبناء، والراتب السنوي. قد يبدو برنامج الاختبار كما يلي:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
Imports Microsoft.VisualBasic
 
Module test
    Sub Main()
        ' interactive tax calculator
        ' the user enters three data points on the keyboard: married nbEnfants salary
        ' the program then displays the tax payable
        Const syntaxe As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
 
        ' data tables required for tax calculation
        Dim limites() As Decimal = {12620D, 13190D, 15640D, 24740D, 31810D, 39970D, 48360D, 55790D, 92970D, 127860D, 151250D, 172040D, 195000D, 0D}
        Dim coeffR() As Decimal = {0D, 0.05D, 0.1D, 0.15D, 0.2D, 0.25D, 0.3D, 0.35D, 0.4D, 0.45D, 0.5D, 0.55D, 0.6D, 0.65D}
        Dim coeffN() As Decimal = {0D, 631D, 1290.5D, 2072.5D, 3309.5D, 4900D, 6898.5D, 9316.5D, 12106D, 16754.5D, 23147.5D, 30710D, 39312D, 49062D}
 
        ' tax object creation
        Dim objImpôt As impot = Nothing
        Try
            objImpôt = New impot(limites, coeffR, coeffN)
        Catch ex As Exception
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
            Environment.Exit(1)
        End Try
        ' infinite loop
        Dim marié As String
        Dim nbEnfants As Integer
        Dim salaire As Long
        While True
            ' tax calculation parameters are requested
            Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
            Dim paramètres As String = Console.In.ReadLine().Trim()
            ' anything to do?
            If paramètres Is Nothing OrElse paramètres = "" Then
                Exit While
            End If
            ' check the number of arguments in the input line
            Dim erreur As Boolean = False
            Dim args As String() = paramètres.Split(Nothing)
            Dim nbParamètres As Integer = args.Length
            If nbParamètres <> 3 Then
                Console.Error.WriteLine(syntaxe)
                erreur = True
            End If
            ' checking parameter validity
            If Not erreur Then
                ' married
                marié = args(0).ToLower()
                If marié <> "o" And marié <> "n" Then
                    erreur = True
                End If
                ' nbEnfants
                Try
                    nbEnfants = Integer.Parse(args(1))
                    If nbEnfants < 0 Then
                        Throw New Exception
                    End If
                Catch
                    erreur = True
                End Try
                ' salary
                Try
                    salaire = Integer.Parse(args(2))
                    If salaire < 0 Then
                        Throw New Exception
                    End If
                Catch
                    erreur = True
                End Try
            End If
            ' if the parameters are correct - the tax is calculated
            If Not erreur Then
                Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire) & " F"))
            Else
                Console.Error.WriteLine(syntaxe)
            End If
        End While
    End Sub
End Module

فيما يلي مثال على البرنامج السابق أثناء العمل:

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 :