Paramètres de méthode (référence C#)

En C#, les arguments peuvent être passés aux paramètres par valeur ou par référence. N’oubliez pas que les types C# peuvent être des types référence (class) ou des types valeur (struct) :

  • Passer par valeur signifie passer une copie de la variable à la méthode.
  • Passer par référence signifie passer l’accès à la variable à la méthode.
  • Une variable d’un type référence contient une référence à ses données.
  • Une variable d’un type valeur contient ses données directement.

Étant donné qu’un struct est un type valeur, quand vous passez un struct par valeur à une méthode, la méthode reçoit et traite une copie de l’argument struct. La méthode n’a pas accès au struct d’origine dans la méthode d’appel, et ne peut donc pas le modifier de quelque manière que ce soit. La méthode peut modifier uniquement la copie.

Une instance de classe est un type référence, pas un type valeur. Quand un type référence est passé par valeur à une méthode, celle-ci reçoit une copie de la référence à l’instance de classe. Autrement dit, la méthode appelée reçoit une copie de l’adresse de l’instance, et la méthode appelante conserve l’adresse d’origine de l’instance. L’instance de classe dans la méthode d’appel a une adresse, le paramètre dans la méthode appelée a une copie de l’adresse, et les deux adresses font référence au même objet. Étant donné que le paramètre contient uniquement une copie de l’adresse, la méthode appelée ne peut pas modifier l’adresse de l’instance de la classe dans la méthode. Toutefois, la méthode appelée peut utiliser la copie de l’adresse pour accéder aux membres de la classe qui ont à la fois l’adresse d’origine et la copie de la référence d’adresse. Si la méthode appelée modifie un membre de classe, l’instance de classe d’origine dans la méthode d’appel change également.

La sortie de l’exemple suivant illustre la différence. La valeur du champ willIChange de l’instance de classe est changée par l’appel à la méthode ClassTaker, car la méthode utilise l’adresse mentionnée dans le paramètre pour rechercher le champ spécifié de l’instance de classe. Le champ willIChange du struct de la méthode d’appel n’est pas changé par l’appel à la méthode StructTaker, car la valeur de l’argument est une copie du struct lui-même, et non pas une copie de son adresse. StructTaker change la copie, et cette dernière est perdue quand l’appel à StructTaker est terminé.

class TheClass
{
    public string? willIChange;
}

struct TheStruct
{
    public string willIChange;
}

class TestClassAndStruct
{
    static void ClassTaker(TheClass c)
    {
        c.willIChange = "Changed";
    }

    static void StructTaker(TheStruct s)
    {
        s.willIChange = "Changed";
    }

    public static void Main()
    {
        TheClass testClass = new TheClass();
        TheStruct testStruct = new TheStruct();

        testClass.willIChange = "Not Changed";
        testStruct.willIChange = "Not Changed";

        ClassTaker(testClass);
        StructTaker(testStruct);

        Console.WriteLine("Class field = {0}", testClass.willIChange);
        Console.WriteLine("Struct field = {0}", testStruct.willIChange);

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    Class field = Changed
    Struct field = Not Changed
*/

Les modifications apportées à l’argument qui sont visibles à partir de l’appelant dépendent de la façon dont un argument est passé et de s’il s’agit d’un type référence ou d’un type valeur.

Passer un type valeur par valeur

Lorsque vous passez un type valeurpar valeur :

  • Si la méthode affecte le paramètre pour faire référence à un autre objet, ces modifications ne sont pas visibles à partir de l’appelant.
  • Si la méthode modifie l’état de l’objet auquel le paramètre fait référence, ces modifications ne sont pas visibles par l’appelant.

L’exemple suivant illustre le passage de paramètres de type valeur par valeur. La variable n est passée par valeur à la méthode SquareIt. Toutes les modifications qui ont lieu à l’intérieur de la méthode n’ont aucun effet sur la valeur d’origine de la variable.

int n = 5;
System.Console.WriteLine("The value before calling the method: {0}", n);

SquareIt(n);  // Passing the variable by value.
System.Console.WriteLine("The value after calling the method: {0}", n);

// Keep the console window open in debug mode.
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();

static void SquareIt(int x)
// The parameter x is passed by value.
// Changes to x will not affect the original value of x.
{
    x *= x;
    System.Console.WriteLine("The value inside the method: {0}", x);
}
/* Output:
    The value before calling the method: 5
    The value inside the method: 25
    The value after calling the method: 5
*/

La variable n est un type valeur. Elle contient ses données, la valeur 5. Quand SquareIt est appelée, le contenu de n est copié dans le paramètre x, qui est mis au carré à l’intérieur de la méthode. Dans Main, toutefois, la valeur de n est identique avant et après l’appel de la méthode SquareIt. La modification qui intervient à l’intérieur de la méthode affecte uniquement la variable locale x.

Passer un type valeur par référence

Lorsque vous transmettez un type valeurpar référence :

  • Si la méthode affecte le paramètre pour faire référence à un autre objet, ces modifications ne sont pas visibles à partir de l’appelant.
  • Si la méthode modifie l’état de l’objet auquel le paramètre fait référence, ces modifications sont visibles à partir de l’appelant.

L’exemple suivant est identique au précédent, sauf que l’argument est passé en tant que paramètre ref. La valeur de l’argument sous-jacent, n, est modifiée quand x est modifiée dans la méthode.

int n = 5;
System.Console.WriteLine("The value before calling the method: {0}", n);

SquareIt(ref n);  // Passing the variable by reference.
System.Console.WriteLine("The value after calling the method: {0}", n);

// Keep the console window open in debug mode.
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();

static void SquareIt(ref int x)
// The parameter x is passed by reference.
// Changes to x will affect the original value of x.
{
    x *= x;
    System.Console.WriteLine("The value inside the method: {0}", x);
}
/* Output:
    The value before calling the method: 5
    The value inside the method: 25
    The value after calling the method: 25
*/

Dans cet exemple, ce n’est pas la valeur de n qui est passée, mais plutôt une référence à n. Le paramètre x n’est pas un int ; il s’agit d’une référence à un int, dans le cas présent une référence à n. Ainsi, quand x est mis au carré à l’intérieur de la méthode, ce qui est réellement mis au carré est ce à quoi x fait référence, c’est-à-dire n.

Passer un type référence par valeur

Lorsque vous passez un type référencepar valeur :

  • Si la méthode affecte le paramètre pour faire référence à un autre objet, ces modifications ne sont pas visibles à partir de l’appelant.
  • Si la méthode modifie l’état de l’objet auquel le paramètre fait référence, ces modifications sont visibles à partir de l’appelant.

L’exemple suivant illustre le passage d’un paramètre de type référence (arr) par valeur, à une méthode (Change). Sachant que le paramètre est une référence à arr, il est possible de modifier les valeurs des éléments du tableau. Cependant, la tentative de réassignation du paramètre à un emplacement de mémoire différent n’aboutit qu’à l’intérieur de la méthode et n’affecte pas la variable d’origine, arr.

int[] arr = { 1, 4, 5 };
System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr[0]);

Change(arr);
System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr[0]);

static void Change(int[] pArray)
{
    pArray[0] = 888;  // This change affects the original element.
    pArray = new int[5] { -3, -1, -2, -3, -4 };   // This change is local.
    System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
}
/* Output:
    Inside Main, before calling the method, the first element is: 1
    Inside the method, the first element is: -3
    Inside Main, after calling the method, the first element is: 888
*/

Dans l’exemple précédent, le tableau arr, qui est un type référence, est passé à la méthode sans le paramètre ref. Dans ce cas, une copie de la référence, qui pointe vers arr, est passée à la méthode. La sortie montre que la méthode peut modifier le contenu d’un élément du tableau, dans ce cas de 1 à 888. Cependant, si vous allouez une nouvelle portion de mémoire via l’opérateur new à l’intérieur de la méthode Change, la variable pArray fait référence à un nouveau tableau. Par conséquent, les modifications apportées à la suite de cette opération n’ont pas d’effet sur le tableau d’origine (arr), qui est créé à l’intérieur de Main. En fait, deux tableaux sont créés dans cet exemple : un à l’intérieur de Main et un autre à l’intérieur de la méthode Change.

Passer un type référence par référence

Lorsque vous passez un type référencepar référence :

  • Si la méthode affecte le paramètre pour faire référence à un autre objet, ces modifications sont visibles à partir de l’appelant.
  • Si la méthode modifie l’état de l’objet auquel le paramètre fait référence, ces modifications sont visibles à partir de l’appelant.

L’exemple suivant est identique au précédent, sauf que le mot clé ref est ajouté à l’en-tête et à l’appel de la méthode. Les modifications qui se produisent dans la méthode affectent la variable d’origine dans le programme appelant.

int[] arr = { 1, 4, 5 };
System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr[0]);

Change(ref arr);
System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr[0]);

static void Change(ref int[] pArray)
{
    // Both of the following changes will affect the original variables:
    pArray[0] = 888;
    pArray = new int[5] { -3, -1, -2, -3, -4 };
    System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
}
/* Output:
    Inside Main, before calling the method, the first element is: 1
    Inside the method, the first element is: -3
    Inside Main, after calling the method, the first element is: -3
*/

Les modifications qui se produisent à l’intérieur de la méthode affectent le tableau d’origine dans Main. En fait, le tableau d’origine est réalloué à l’aide de l’opérateur new. Par conséquent, après l’appel à la méthode Change, toute référence à arr pointe vers le tableau à cinq éléments, qui est créé dans la méthode Change.

Étendue des références et des valeurs

Les méthodes peuvent stocker les valeurs des paramètres dans des champs. Lorsque les paramètres sont passés par valeur, cela est toujours fait de façon sécurisée. Les valeurs sont copiées et les types de référence sont accessibles lorsqu’ils sont stockés dans un champ. La transmission de paramètres par référence de façon sûre nécessite que le compilateur définisse quand il est sûr d’affecter une référence à une nouvelle variable. Pour chaque expression, le compilateur définit une étendue qui limite l’accès à une expression ou à une variable. Le compilateur utilise deux étendues : safe_to_escape et ref_safe_to_escape.

  • L’étendue safe_to_escape définit l’étendue dans laquelle n’importe quelle expression est accessible en toute sécurité.
  • L’étendue ref_safe_to_escape définit l’étendue dans laquelle une référence à n’importe quelle expression peut être accessible ou modifiée en toute sécurité.

De manière informelle, vous pouvez considérer ces étendues comme le mécanisme permettant de vous assurer que votre code n’accède jamais ou ne modifie jamais une référence qui n’est plus valide. Une référence est valide tant qu’elle fait référence à un objet ou à un struct valide. L’étendue safe_to_escape définit quand une variable peut être affectée ou réaffectée. L’étendue ref_safe_to_escape définit quand une variable peut être affectée par référence ou réaffectée par référence. L’affectation affecte une variable à une nouvelle valeur ; l’affectation par référence affecte la variable pour faire référence à un autre emplacement de stockage.

Modificateurs

Les paramètres déclarés pour une méthode sans in, ref ou out sont passés à la méthode appelée par valeur. Les modificateurs ref, in et out diffèrent dans les règles d’affectation :

  • L’argument d’un paramètre ref doit être définitivement affecté. La méthode appelée peut réaffecter ce paramètre.
  • L’argument d’un paramètre in doit être définitivement affecté. La méthode appelée ne peut pas réaffecter ce paramètre.
  • L’argument d’un paramètre out n’a pas besoin d’être définitivement affecté. La méthode appelée doit affecter le paramètre.

Cette section décrit les mots clés que vous pouvez utiliser pour déclarer des paramètres de méthode :

  • params spécifie que ce paramètre peut prendre un nombre variable d’arguments.
  • in spécifie que ce paramètre est passé par référence, mais qu’il est lu uniquement par la méthode appelée.
  • ref spécifie que ce paramètre est passé par référence et qu’il peut être lu ou écrit par la méthode appelée.
  • out spécifie que ce paramètre est passé par référence et qu’il est écrit par la méthode appelée.

Voir aussi