Si je passe un objet à une méthode, pourquoi devrais-je utiliser le mot clé ref? N'est-ce pas le comportement par défaut de toute façon?
Par exemple:
class Program
{
static void Main(string[] args)
{
TestRef t = new TestRef();
t.Something = "Foo";
DoSomething(t);
Console.WriteLine(t.Something);
}
static public void DoSomething(TestRef t)
{
t.Something = "Bar";
}
}
public class TestRef
{
public string Something { get; set; }
}
Le résultat est "Bar", ce qui signifie que l'objet a été passé en tant que référence.
Passez un ref
si vous voulez changer l’objet:
TestRef t = new TestRef();
t.Something = "Foo";
DoSomething(ref t);
void DoSomething(ref TestRef t)
{
t = new TestRef();
t.Something = "Not just a changed t, but a completely different TestRef object";
}
Après avoir appelé DoSomething, t
ne fait pas référence à l'original new TestRef
, mais à un objet complètement différent.
Cela peut également être utile si vous souhaitez modifier la valeur d'un objet immuable, par exemple. string
. Vous ne pouvez pas modifier la valeur d'un string
une fois qu'il a été créé. Mais en utilisant un ref
, vous pouvez créer une fonction qui change la chaîne en une autre avec une valeur différente.
Edit: Comme d'autres personnes l'ont mentionné. Ce n'est pas une bonne idée d'utiliser ref
sauf si cela est nécessaire. Utiliser ref
donne à la méthode la liberté de changer l'argument pour autre chose. Les appelants de la méthode devront être codés pour s'assurer qu'ils gèrent cette possibilité.
De même, lorsque le type de paramètre est un objet, les variables d'objet agissent toujours comme des références à l'objet. Cela signifie que lorsque le mot clé ref
est utilisé, vous avez une référence à une référence. Cela vous permet de faire les choses décrites dans l'exemple ci-dessus. Toutefois, lorsque le type de paramètre est une valeur primitive (par exemple, int
), si ce paramètre est affecté à la méthode, la valeur de l'argument transmis est modifiée après le retour de la méthode:
int x = 1;
Change(ref x);
Debug.Assert(x == 5);
WillNotChange(x);
Debug.Assert(x == 5); // Note: x doesn't become 10
void Change(ref int x)
{
x = 5;
}
void WillNotChange(int x)
{
x = 10;
}
Vous devez faire la distinction entre "passer une référence par valeur" et "passer un paramètre/argument par référence".
J'ai écrit un article assez long sur le sujet pour éviter d'avoir à écrire avec soin chaque fois que cela apparaît dans les groupes de discussion :)
Dans .NET, lorsque vous transmettez un paramètre à une méthode, une copie est créée. Dans les types de valeur, cela signifie que toute modification apportée à la valeur se situe dans l'étendue de la méthode et est perdue lorsque vous quittez la méthode.
Lors de la transmission d'un type de référence, une copie est également créée, mais il s'agit d'une copie d'une référence, c'est-à-dire que vous avez maintenant DEUX références en mémoire pour le même objet. Donc, si vous utilisez la référence pour modifier l'objet, il sera modifié. Mais si vous modifiez la référence elle-même (nous devons nous rappeler que c'est une copie), toutes les modifications sont également perdues à la sortie de la méthode.
Comme on l'a déjà dit, une assignation est une modification de la référence, elle est donc perdue:
public void Method1(object obj) {
obj = new Object();
}
public void Method2(object obj) {
obj = _privateObject;
}
Les méthodes ci-dessus ne modifient pas l'objet d'origine.
Une petite modification de votre exemple
using System;
class Program
{
static void Main(string[] args)
{
TestRef t = new TestRef();
t.Something = "Foo";
DoSomething(t);
Console.WriteLine(t.Something);
}
static public void DoSomething(TestRef t)
{
t = new TestRef();
t.Something = "Bar";
}
}
public class TestRef
{
private string s;
public string Something
{
get {return s;}
set { s = value; }
}
}
Puisque TestRef est une classe (qui sont des objets de référence), vous pouvez modifier le contenu à l'intérieur de t sans le transmettre en tant que réf. Cependant, si vous passez t en tant que ref, TestRef peut changer le nom du t original. c'est-à-dire qu'il pointe vers un objet différent.
Avec ref
vous pouvez écrire:
static public void DoSomething(ref TestRef t)
{
t = new TestRef();
}
Et t sera changé une fois la méthode terminée.
Pensez aux variables (par exemple, foo
) des types de référence (par exemple, List<T>
) en tant que holding identificateurs d'objet de la forme "Objet # 24601". Supposons que l'instruction foo = new List<int> {1,5,7,9};
force foo
à contenir "Object # 24601" (une liste de quatre éléments). Ensuite, l'appel de foo.Length
demandera sa longueur à l'objet n ° 24601, qui répondra 4, ainsi foo.Length
sera égal à 4.
Si foo
est transmis à une méthode sans utiliser ref
, cette méthode peut modifier l'objet n ° 24601. En conséquence de ces modifications, il est possible que foo.Length
ne soit plus égal à 4. Cependant, la méthode elle-même ne pourra pas changer foo
, qui conservera toujours "Objet # 24601".
Passer foo
en tant que paramètre ref
permettra à la méthode appelée d'apporter des modifications non seulement à l'objet # 24601, mais également à foo
lui-même. La méthode peut créer un nouvel objet # 8675309 et stocker une référence à celui-ci dans foo
. Dans ce cas, foo
ne contiendrait plus d'objet "24601", mais "d'objet 8675309".
En pratique, les variables de type référence ne contiennent pas de chaînes de la forme "Objet # 8675309"; ils ne tiennent même pas quelque chose qui puisse être converti de manière significative en un nombre. Bien que chaque variable de type référence contienne un modèle de bits, il n'y a pas de relation fixe entre les modèles de bits stockés dans ces variables et les objets qu'elles identifient. Il n’existe aucun moyen permettant à un code d’extraire des informations d’un objet ou une référence à celui-ci et de déterminer ultérieurement si une autre référence identifie le même objet, à moins que le code ne contienne ou ne connaisse une référence qui identifie l’objet original.
C'est comme passer un pointeur à un pointeur en C. Dans .NET, cela vous permettra de changer ce à quoi le T original se réfère, personnellement bien que je pense que si vous faites cela dans .NET, vous avez probablement Vous avez un problème de conception!
ref
imite (ou se comporte) comme une zone globale pour seulement deux domaines:
En utilisant le mot clé ref
avec des types de référence, vous passez effectivement une référence à la référence. À bien des égards, cela revient à utiliser le mot clé out
, à la différence près que rien ne garantit que la méthode affectera réellement quoi que ce soit au paramètre ref
'ed.
Si vous passez une valeur, cependant, les choses sont différentes. Vous pouvez forcer une valeur à être passée par référence. Cela vous permet de transmettre un entier à une méthode, par exemple, et de lui demander de modifier l'entier en votre nom.
Ref indique si la fonction peut mettre la main sur l'objet lui-même ou seulement sur sa valeur.
Passer par référence n'est pas lié à une langue; c'est une stratégie de liaison de paramètre à côté de passer par valeur, passer par nom, passer par besoin, etc.
Sidenote: le nom de la classe TestRef
est un choix terriblement mauvais dans ce contexte;).