mardi 25 mars 2014

7 - Les méthodes

0 commentaires
Les méthodes (aussi appelées "fonctions" dans de nombreux langages) sont là pour nous simplifier la vie : plutôt que de mettre tout le code à la suite, on structure notre programme en créant des méthodes.
Une méthode est une suite d'instructions regroupées sous un nom ; elle prend en entrée desparamètres et retourne un résultat. Notez qu'une méthode peut ne prendre aucun paramètre en entrée ou ne retourner aucun résultat.
Les méthodes ont de nombreux avantages. Le premier est sans doute de ne pas avoir à réécrire beaucoup de lignes de code à chaque fois qu'on veut faire une certaine opération. Cela permet aussi de rajouter un aspect dynamique au code : suivant les paramètres passés, le résultat retourné n'est pas le même.
Voici la syntaxe pour une méthode prenant deux paramètres en entrée et ne retournant rien :
void nomDeLaMéthode(typeDu1erParamètre nomDu1erParamètre, typeDu2eParamètre nomDu2eParamètre)
{
    // Code à exécuter quand la méthode est appelée.
}
Voici la syntaxe pour une méthode ne prenant aucun paramètre en entrée et retournant xxx (xxx étant une variable) :
typeDuRésultat nomDeLaMéthode()
{
    // Code à exécuter quand la méthode est appelée.
    return xxx;
}
1void
veut dire "vide", "dépourvu de", ... Quand une méthode ne retourne rien, le type de retour est doncvoid.


Besoin d'exemples ?
Pas de soucis ! Créons une méthode qui multiplie un nombre x par un nombre y (nous allons travailler avec des entiers). Voici comment faire :
1int Multiply(int x, int y)
2{
3    return x*y;
4}
Nous allons utiliser une méthode déjà créée qui va afficher du texte dans la console, c'est WriteLine. Cette méthode se situe dans la classe Console, donc pour y accéder il faut écrireConsole.WriteLine. Vous aurez plus d'explications quand nous verrons les classes et vous pourrez ainsi mieux comprendre. ;) Pour l'instant, regardez comment ça fonctionne dans les exemples suivants.
Voici une autre méthode, qui affiche un Hello World! dans le cadre "Output" de Visual Studio si vous avez un programme avec des fenêtres (par exemple des WinForms), et dans la console dans le cas contraire.
Rappel : la console, plus connue sous le nom d'éditeur de commandes, est la fenêtre où du code est écrit en général en vert sur fond noir.
1void SayHello()
2{
3    Console.WriteLine("Hello world!");
4}

Vous noterez la magnifique transition vers l'appel d'une méthode. Eh oui,Console.WriteLine("Hello world!"); est bien un appel une méthode !
Console.WriteLine("Hello world!"); est un appel à la la méthode WriteLine, à laquelle nous passons en paramètre une chaîne de caractères qui vaut "Hello world!".
L'appel se fait comme ceci : nomDeLaMéthode(paramètresSéparésParUneVirgule);.
Pourquoi c'est Console.WriteLine au lieu de simplement WriteLine ?
Console est une classe dans laquelle est définie la méthode WriteLine. Ne nous attardons pas sur ce point qui fera l'objet d'explications détaillées dans la suite de cette 1ère partie théorique.
On pourra donc écrire (si l'on a créé la méthode Multiply) :
1int number1 = 3;
2int number2 = 2;
3int number3 = Multiply(number1, number2);
4Console.WriteLine(number3);
Que se passe-t-il à la ligne no3 ?
number1 et number2 sont évalués : on va récupérer en mémoire leur valeur, que l'on passe à la méthode. La méthode fait le calcul et renvoie le résultat (ici 6). L'expression Multiply(number1, number2) est donc évaluée comme un entier valant 6. Ensuite on fixe la valeur de number3 à 6.
Pour simplifier, écrivez :
1Console.WriteLine(Multiply(3, 2));
Comme bien souvent, le code se lit de droite à gauche.
On appelle la méthode Multiply à laquelle on passe les nombres 3 et 2, compris par défaut comme des entiers. Multiply passe son résultat en tant que paramètre à WriteLine qui affichera donc 3*2, soit 6.

Passage par valeur

Par défaut, les paramètres des méthodes sont toujours passés par valeur. Cela signifie qu'une copie de la valeur de chaque paramètre est faite au moment de l'appel et ce sont ces copies qui sont utilisées dans le corps de la méthode : les variables transmises à la méthode ne peuvent donc pas être modifiées par celle-ci. Si vous modifiez ces valeurs dans le corps de la méthode, les modifications seront perdues à la fin de son appel.
En réalité ce comportement n'a rien de sorcier : c'est exactement ce qui se passe lorsque vous initialisez une variable à partir d'une autre variable. La valeur est copiée au moment de l'initialisation, puis les deux variables vivent leur vie indépendamment. Voici un exemple rapide pour le prouver :
1int myInt = 100;
2// On copie la valeur, donc myOtherInt vaut 100.
3int myOtherInt = myInt;
4Console.WriteLine("myInt = " + myInt);
5Console.WriteLine("myOtherInt = " + myOtherInt);
6
7// On modifie myOtherInt.
8myOtherInt = 5;
9Console.WriteLine("myInt = " + myInt);
10Console.WriteLine("myOtherInt = " + myOtherInt);
Après exécution, vous aurez :
myInt = 100
myOtherInt = 100
myInt = 100
myOtherInt = 5
Pas de surprise donc. ;)
Introduisons maintenant un petit peu de vocabulaire pour aider à comprendre la suite :
  • Les paramètres transmis à la méthode (ceux qui sont copiés) sont appelés paramètres effectifs.
  • Les paramètres utilisés dans le corps de la méthode (ceux qui reçoivent la copie) sont appelésparamètres formels.
Les paramètres formels doivent avoir le même type que les paramètres effectifs (ou un type compatible), mais pas nécessairement le même nom.
Voyons ce qui se passe quand on modifie la valeur d'un paramètre formel. Voici une méthodeChangeInt qui prend un entier en paramètre, le modifie, et affiche son contenu :
1void ChangeInt(int myParameter)
2{
3    Console.WriteLine("Le paramètre formel myParameter vaut : " + myParameter);
4    myParameter = 5;
5    Console.WriteLine("Le paramètre formel myParameter vaut : " + myParameter);
6}
Voici comment vous pouvez l'utiliser :
1int myInt = 100;
2Console.WriteLine("Le paramètre effectif myInt vaut : " + myInt);
3ChangeInt(myInt);
4Console.WriteLine("Le paramètre effectif myInt vaut : " + myInt);
Après exécution, vous aurez :
Le paramètre effectif myInt vaut : 100
Le paramètre formel myParameter vaut : 100
Le paramètre formel myParameter vaut : 5
Le paramètre effectif myInt vaut : 100
1myInt
n'a pas été changé car on n'a fait que modifier myParameter, qui ne contenait qu'une copie de sa valeur. Le comportement observé est le même que dans l'exemple donné précédemment.
Il peut cependant être utile de modifier un paramètre effectif depuis une méthode, c'est-à-dire de répercuter les changements sur le paramètre qui a été transmis. Pour cela, on utilise le passage par référence.

Passage par référence

Lorsqu'un paramètre est passé par référence, il est lui-même utilisé dans le corps de la méthode. Les paramètres effectif et formel ne font plus qu'un. Il est donc possible de les modifier dans le corps de la méthode et les changements seront maintenus après l'appel.
Pour forcer le passage par référence, utilisez le mot-clef ref.

ref

Reprenons l'exemple en modifiant la méthode ChangeInt :
1// Notez l'ajout de ref.
2void ChangeInt(ref int myParameter)
3{
4    Console.WriteLine("Le paramètre formel myParameter vaut : " + myParameter);
5    myParameter = 5;
6    Console.WriteLine("Le paramètre formel myParameter vaut : " + myParameter);
7}
Voici comment vous pouvez l'utiliser :
1int myInt = 100;
2Console.WriteLine("Le paramètre effectif myInt vaut : " + myInt);
3// On utilise ref aussi pour l'appel.
4ChangeInt(ref myInt);
5Console.WriteLine("Le paramètre effectif myInt vaut : " + myInt);
Après exécution, vous aurez :
Le paramètre effectif myInt vaut : 100
Le paramètre formel myParameter vaut : 100
Le paramètre formel myParameter vaut : 5
Le paramètre effectif myInt vaut : 5
L'utilisation de ref impose que le paramètre effectif soit une variable correctement initialisée. Vous ne pourrez donc pas compiler les exemples de code suivant :
1int myInt = 5;
2// Erreur du compilateur : "Argument 1 must be passed with the 'ref' keyword".
3ChangeInt(myInt);
1// On ne fait que déclarer l'entier, sans l'initialiser.
2int myInt;
3// Erreur du compilateur : "Use of unassigned local variable 'myInt'".
4ChangeInt(ref myInt);
1// On essaie de transmettre une valeur directement (ici la valeur 5).
2// Erreur du compilateur : "A ref or out argument must be an assignable variable".
3ChangeInt(ref 5);
Il peut arriver qu'un paramètre serve uniquement à récupérer une valeur initialisée par la méthode. C'est notamment utile lorsqu'une méthode doit renvoyer plusieurs valeurs. Pour ces cas-là, on utilise le mot-clef out.

out

L'utilisation de out impose à la méthode d'assigner une valeur au paramètre avant de se terminer. Et contrairement à ref, il n'est pas obligatoire d'assigner une valeur au paramètre avant de le transmettre à la méthode : la valeur transmise ne sera de toute façon pas utilisable dans la méthode.
Reprenons le même exemple, en utilisant out cette fois :
1// Notez l'utilisation de out.
2void ChangeInt(out int myParameter)
3{
4    // On ne peut pas afficher la valeur de myParameter avant la prochaine ligne de code,
5    // car il serait considéré comme non assigné. On réalise alors l'assignation.
6    myParameter = 5;
7    Console.WriteLine("Le paramètre formel myParameter vaut : " + myParameter);
8}
Voici comment vous pouvez l'utiliser :
1// On ne fait que déclarer l'entier, sans l'initialiser.
2int myInt;
3// On utilise out aussi pour l'appel.
4ChangeInt(out myInt);
5Console.WriteLine("Le paramètre effectif myInt vaut : " + myInt);
Après exécution, vous aurez :
Le paramètre formel myParameter vaut : 5
Le paramètre effectif myInt vaut : 5
En pratique, out est utilisé beaucoup plus souvent que ref, car il arrive fréquemment qu'une méthode doive retourner plus d'une valeur. Par exemple, la méthode int.TryParse sert à convertir une chaîne de caractères en entier. Elle renvoie deux valeurs : un entier (le résultat de la conversion), et un booléen (qui indique si la conversion a réussi ou non). Elle s'utilise comme ceci :

1int myInt;
2bool success = int.TryParse("1234", out myInt);
3if(success)
4{
5    Console.WriteLine("La conversion a réussi, le double du nombre est : " + myInt * 2 + ".");
6}
7else
8{
9    Console.WriteLine("La conversion a échoué.");
10}

La surcharge permet de créer des méthodes qui font globalement la même chose en leur donnant le même nom.
Elles doivent cependant avoir des signatures différentes. La signature d'une méthode permet de l'identifier ; elle dépend de deux choses : son nom et ses arguments (mais pas son type de retour !). Pour la surcharge, les méthodes doivent donc être différentiables par au moins une des propositions suivantes :
  • le nombre de paramètres ;
  • le type de chaque paramètre.
Dans l'exemple suivant, on crée deux méthodes SayHello dont l'une, une fois appelée, affichera juste"Hello!" (on ne lui passe aucun paramètre), et l'autre affichera par exemple "Hello John!" si on lui passe en paramètre la chaîne de caractères "John" :
1void SayHello()
2{
3    Console.WriteLine("Hello!");
4}
5
6void SayHello(string name)
7{
8    Console.WriteLine("Hello " + name + "!");
9}

Je sais que je n'arrête pas de vous le dire, mais là encore, c'est une notion fondamentale ! Elle existe d'ailleurs dans de nombreux langages (dans lesquels on utilise plus le mot "fonction" que "méthode").
Vous verrez en codant que les méthodes se révèlent très utiles. ;)







Leave a Reply