Question 23

Quelle est la différence entre les mots-clés "ByVal" et "ByRef" ?

Notion de passage "par valeur" et de passage "par référence"

Les langages de programmation (dont VB) permettent en général de transmettre des arguments aux fonctions. On distingue 2 types de passage (vue simplifiée) :
  • En utilisant ByVal, la fonction reçoit une "copie" de la variable. C'est un passage par valeur.
  • En utilisant ByRef, la fonction reçoit "vraiment" la variable. C'est un passage par référence.

D'un point de vue technique, lors d'un passage par valeur, la fonction reçoit simplement le contenu ou valeur de la variable passée en paramètre. A l'inverse, lors d'un passage par référence, la fonction reçoit en réalité l'adresse de la variable passée en paramètre : les modifications du paramètre formel affectent directement la variable paramètre.

La différence est fondamentale : dans le premier cas, si la fonction modifie la valeur de sa variable interne, cela n'affecte en rien la variable de l'appelant. Au retour de la fonction, la variable n'est pas modifiée.

A l'inverse, si on effectue un passage par référence, toute modification de la variable au sein de la fonction va réellement modifier la variable passée en paramètre. Au retour de la fonction, la variable sera modifiée.

En VB, l'usage de ByVal et ByRef est optionnel.
Si l'on n'indique rien, les variables sont passées par référence (ByRef). Les 2 déclarations suivantes sont synonymes :

    ' passage explicite par référence
    Function Sample(ByRef n As Integer) As Integer
    
    ' Identique à la précédente : n est passé par référence
    Function Sample(n As Integer) As Integer

Il est très fortement recommandé de toujours utiliser explicitement ByVal ou ByRef, afin d'exprimer l'intention du programmeur. En outre, préciser ByVal quand cela est nécessaire permet de protéger le programme contre une modification non désirée d'une variable paramètre. Le choix de VB de passer par défaut les paramètres par référence s'explique par des raisons historiques, mais est potentiellement dangereux.

Exemples

Considérons le code suivant :

Private Sub Form_Load()

    Dim n As Long
    
    n = 100
    TestByVal n
    Debug.Print n ' Imprime 100, n n'est PAS modifiée

End Sub

Private Sub TestByVal(ByVal Arg1 As Long)

    Arg1 = Arg1 - 50
End Sub

Lors de l'appel à la procédure TestByVal, n est transmis par valeur. Ainsi toute modification apportée à Arg1 n'aura aucun effet sur n, Arg1 étant en fait une copie de n.

Voyons maintenant ce qui se passe lorsque l'on passe un argument par référence :

Private Sub Form_Load()

    Dim n As Long
    n = 100
    TestByVal n
    Debug.Print n ' Imprime 50, la variable 'n' a été modifiée

End Sub

Private Sub TestByVal(ByRef Arg1 As Long)

    Arg1 = Arg1 - 50

End Sub

Dans ce cas, toute modification apportée à Arg1, affectera directement la variable n, Arg1 étant en fait un "alias" de n qui contient l'adresse de n.

Remarques

Comme indiqué précédemment, les arguments sont passés par défaut par référence. Il est conseillé de toujours mentionner explicitement ByVal ou ByRef. La règle est très simple : Si la fonction n'a pas de raison de modifier un argument, on le passe par valeur. On ne passe explicitement par référence que les variables qui doivent être modifiées.

Valeurs de retour

L'un des usages les plus courants du passage par référence est le retour de plusieurs valeurs, simultanément. En effet, le passage par référence modifie la variable de l'appelant et permet donc un retour. L'exemple suivant montre comment utiliser cette technique pour calculer les racines réelles d'un polynôme du second degré :

Private Function Poly2(ByVal a As Double, ByVal b As Double, ByVal c As Double, ByRef x1 As Double, x2 As Double) As Boolean
    Dim Delta As Double
    Dim Result As Boolean
    
    'Trouve les racines réelles x1 et x2 du polynome du second degré ax²+bx+c
    
    Result = False
    Delta = b * b - 4 * a * c
    
    If (Delta >= 0) Then
        x1 = (-b + Sqr(Delta)) / (2 * a)
        x2 = (-b - Sqr(Delta)) / (2 * a)
        Result = True
    End If
    
    Poly2 = Result
End Function

Private Sub Test()
    Dim x1 As Double, x2 As Double
    
    If Poly2(1, 0, -1, x1, x2) Then
        MsgBox "Les racines sont : " & x1 & " et " & x2
    End If
End Sub

Il est à remarquer que l'exemple précédent ne retourne pas deux mais bien trois valeurs : les deux racines et un booléen permettant de tester le succès de l'opération. De manière tout à fait générale, toujours renvoyer un état de réussite (et détecter les conditions d'échec pour ce faire) est gage de robustesse du code.

Une autre possibilité pour renvoyer plusieurs valeurs est d'utiliser un type de donnée spécialisé, souvent sous la forme un type utilisateur ou d'une classe. Ceci présente le désavantage de demander multiplier le nombre de structures pour contenir des résultats peu complexes, alors que ceux-ci ne sont souvent pas réutilisés en dehors de l'appelant de la fonction. Il est donc raisonnable, si le résultat est relativement simple et n'est que peu ou pas propagé tel quel dans le logiciel, de préférer renvoyer de telles valeurs par références.

Pour aller plus loin

Voir aussi :

Date de publication : 07 juillet 2002
Dernière modification : 13 septembre 2007
Rubriques : Généralités
Mots-clés : ByVal, ByRef, arguments, passage par valeur, passage par référence, variable, variables