Pourquoi utiliser le mot-clé 'ref' lors du passage d'un objet?

290

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; }
}

La sortie est "Bar" ce qui signifie que l'objet a été passé comme référence.

Ryan
la source

Réponses:

298

Passez un refsi vous voulez changer ce qu'est 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, tne fait pas référence à l'original new TestRef, mais fait référence à un objet complètement différent.

Cela peut également être utile si vous souhaitez modifier la valeur d'un objet immuable, par exemple a string. Vous ne pouvez pas modifier la valeur de a une stringfois qu'il a été créé. Mais en utilisant un ref, vous pouvez créer une fonction qui change la chaîne pour une autre qui a une valeur différente.

Edit: Comme d'autres l'ont mentionné. Ce n'est pas une bonne idée d'utiliser à refmoins que cela ne soit nécessaire. L'utilisation refdonne à 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é.

En outre, 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 refmot clé est utilisé, vous avez une référence à une référence. Cela vous permet de faire les choses comme décrit dans l'exemple donné ci-dessus. Mais, lorsque le type de paramètre est une valeur primitive (par exemple int), si ce paramètre est affecté à l'intérieur de la méthode, la valeur de l'argument transmis sera 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;
}
Scott Langham
la source
88

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 soigneusement chaque fois que cela revient sur les newsgroups :)

Jon Skeet
la source
1
Eh bien, j'ai rencontré le problème lors de la mise à niveau de VB6 en code .Net C #. Il existe des signatures de fonction / méthode qui prennent les paramètres ref, out et plain. Alors, comment pouvons-nous mieux distinguer la différence entre un paramètre simple et une référence?
bonCodigo
2
@bonCodigo: Vous ne savez pas ce que vous entendez par "mieux distinguer" - cela fait partie de la signature, et vous devez également spécifier refsur le site de l'appel ... où voudriez-vous le distinguer? La sémantique est également assez claire, mais doit être exprimée avec soin (plutôt que «les objets sont passés par référence», ce qui est la simplification excessive courante).
Jon Skeet
je ne sais pas pourquoi visual studio ne montre toujours pas explicitement ce qui est passé
MonsterMMORPG
3
@ MonsterMMORPG: Je ne sais pas ce que tu veux dire par là, j'ai peur.
Jon Skeet
56

Dans .NET lorsque vous transmettez un paramètre à une méthode, une copie est créée. Dans les types de valeur signifie que toute modification que vous apportez à la valeur est à la portée de la méthode et est perdue lorsque vous quittez la méthode.

Lors du passage d'un type de référence, une copie est également effectué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 est modifié. Mais si vous modifiez la référence elle-même - nous devons nous rappeler que c'est une copie - alors toutes les modifications sont également perdues à la sortie de la méthode.

Comme on l'a déjà dit, une affectation est une modification de la référence, donc se perd:

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; }
        }
    }
Ricardo Amores
la source
6
J'aime mieux cette réponse que la réponse acceptée. Il explique plus clairement ce qui se passe lors du passage d'une variable de type référence avec le mot clé ref. Je vous remercie!
Stefan
17

Puisque TestRef est une classe (qui sont des objets de référence), vous pouvez changer le contenu à l'intérieur de t sans le passer comme référence. Cependant, si vous passez t comme référence, TestRef peut changer ce à quoi le t d'origine fait référence. c'est-à-dire le faire pointer vers un objet différent.

Ferruccio
la source
16

Avec refvous pouvez écrire:

static public void DoSomething(ref TestRef t)
{
    t = new TestRef();
}

Et t sera modifié une fois la méthode terminée.

Rinat Abdullin
la source
8

Considérez les variables (par exemple foo) des types de référence (par exemple List<T>) comme contenant des identifiants d'objet de la forme "Objet # 24601". Supposons que l'instruction foo = new List<int> {1,5,7,9};provoque foole maintien de "Objet # 24601" (une liste avec quatre éléments). Ensuite, l'appel foo.Lengthdemandera à l'objet # 24601 sa longueur, et il répondra 4, donc foo.Lengthégal à 4.

Si fooest passé à une méthode sans l'utiliser ref, cette méthode peut apporter des modifications à l'objet n ° 24601. À la suite de ces changements, il se foo.Lengthpeut qu'ils ne soient plus égaux à 4. La méthode elle-même, cependant, ne pourra pas changer foo, qui continuera à contenir "Objet # 24601".

Le passage fooen refparamètre permettra à la méthode appelée d'apporter des modifications non seulement à l'objet # 24601, mais également à fooelle-même. La méthode peut créer un nouvel objet # 8675309 et stocker une référence à celui-ci dans foo. Si tel est le cas, foone contiendrait plus "Objet # 24601", mais plutôt "Objet # 8675309".

En pratique, les variables de type référence ne contiennent pas de chaînes de la forme "Objet # 8675309"; ils ne contiennent même rien qui puisse être converti de manière significative en nombre. Même si chaque variable de type référence contiendra un motif binaire, il n'y a pas de relation fixe entre les motifs binaires stockés dans ces variables et les objets qu'ils identifient. Il n'y a aucun moyen que le code puisse extraire des informations d'un objet ou d'une référence à celui-ci, et déterminer plus tard si une autre référence a identifié le même objet, à moins que le code ne contienne ou ne connaissait une référence qui a identifié l'objet d'origine.

supercat
la source
5

C'est comme passer un pointeur à un pointeur en C. Dans .NET, cela vous permettra de changer ce à quoi le T d'origine fait référence, personnellement, bien que je pense que si vous le faites dans .NET, vous avez probablement un problème de conception!

pezi_pink_squirrel
la source
3

En utilisant le refmot - clé avec des types de référence, vous transmettez effectivement une référence à la référence. À bien des égards, c'est la même chose que d'utiliser le outmot - clé, mais avec la différence mineure qu'il n'y a aucune garantie que la méthode affectera réellement quelque chose au refparamètre 'ed.

Isak Savo
la source
3

ref imite (ou se comporte) comme une zone globale juste pour deux étendues:

  • Votre interlocuteur
  • Appelé.
guneysus
la source
1

Si vous passez une valeur, cependant, les choses sont différentes. Vous pouvez forcer une valeur à passer par référence. Cela vous permet de passer un entier à une méthode, par exemple, et de demander à la méthode de modifier l'entier en votre nom.

Andrew
la source
4
Que vous passiez une référence ou une valeur de type valeur, le comportement par défaut est de passer par valeur. Vous devez juste comprendre qu'avec les types de référence, la valeur que vous transmettez est une référence. Ce n'est pas la même chose que de passer par référence.
Jon Skeet
1

Ref indique si la fonction peut mettre la main sur l'objet lui-même, ou seulement sur sa valeur.

Le passage par référence n'est pas lié à une langue; c'est une stratégie de liaison de paramètres à côté de pass-by-value, pass by name, pass by need etc ...

Un sidenote: le nom de classe TestRefest un mauvais choix hideux dans ce contexte;).

xtofl
la source