À quoi sert «ref» pour les variables de type référence en C #?

176

Je comprends que si je passe une valeur de type ( int, struct, etc.) comme paramètre (sans le refmot - clé), une copie de cette variable est passée à la méthode, mais si j'utilise le refmot - clé une référence à cette variable est passée, pas un nouveau.

Mais avec les types référence, comme les classes, même sans le refmot - clé, une référence est passée à la méthode, pas une copie. Alors, quelle est l'utilisation du refmot - clé avec des types de référence?


Prends pour exemple:

var x = new Foo();

Quelle est la différence entre ce qui suit?

void Bar(Foo y) {
    y.Name = "2";
}

et

void Bar(ref Foo y) {
    y.Name = "2";
}
Andreas Grech
la source

Réponses:

154

Vous pouvez modifier les foopoints à utiliser y:

Foo foo = new Foo("1");

void Bar(ref Foo y)
{
    y = new Foo("2");
}

Bar(ref foo);
// foo.Name == "2"
user7116
la source
17
donc vous obtenez essentiellement une référence à la référence originale
lhahne
2
Vous pouvez changer ce à quoi la référence originale «fait référence», donc oui.
user7116
1
Chris, votre explication est excellente; Merci de m'aider à comprendre ce concept.
Andreas Grech
4
Donc, utiliser 'ref' sur un objet est comme utiliser des pointeurs doubles en C ++?
Tom Hazel
1
@TomHazel: ish , à condition que vous utilisez des pointeurs « double » en C ++ pour modifier ce que les points de pointeur pour.
user7116
29

Il existe des cas où vous souhaitez modifier la référence réelle et non l'objet pointé:

void Swap<T>(ref T x, ref T y) {
    T t = x;
    x = y;
    y = t;
}

var test = new[] { "0", "1" };
Swap(ref test[0], ref test[1]);
Mehrdad Afshari
la source
21

Jon Skeet a écrit un excellent article sur le passage de paramètres en C #. Il détaille clairement le comportement exact et l'utilisation des paramètres de passage par valeur, par référence ( ref) et par sortie ( out).

Voici une citation importante de cette page concernant les refparamètres:

Les paramètres de référence ne transmettent pas les valeurs des variables utilisées dans l'appel du membre de fonction - ils utilisent les variables elles-mêmes. Plutôt que de créer un nouvel emplacement de stockage pour la variable dans la déclaration de membre de fonction, le même emplacement de stockage est utilisé, de sorte que la valeur de la variable dans le membre de fonction et la valeur du paramètre de référence seront toujours les mêmes. Les paramètres de référence ont besoin du modificateur ref dans le cadre de la déclaration et de l'invocation - cela signifie qu'il est toujours clair lorsque vous passez quelque chose par référence.

Noldorin
la source
11
Je aime l'analogie de passer vos chiens en laisse à un ami pour faire passer une référence par valeur ... il se décompose rapidement cependant, parce que je pense que vous feriez sans doute remarqué si votre ami échangé votre-Shitzu à un doberman avant qu'il ne vous a remis de nouveau the leash ;-)
corlettk
16

Très bien expliqué ici: http://msdn.microsoft.com/en-us/library/s6938f28.aspx

Résumé de l'article:

Une variable d'un type référence ne contient pas directement ses données; il contient une référence à ses données. Lorsque vous passez un paramètre de type référence par valeur, il est possible de modifier les données pointées par la référence, telles que la valeur d'un membre de classe. Cependant, vous ne pouvez pas modifier la valeur de la référence elle-même; autrement dit, vous ne pouvez pas utiliser la même référence pour allouer de la mémoire à une nouvelle classe et la conserver en dehors du bloc. Pour ce faire, transmettez le paramètre à l'aide du mot clé ref ou out.

himanshupareek66
la source
4
L'explication est en effet très agréable. Cependant, les réponses par lien uniquement sont déconseillées sur SO. J'ai ajouté un résumé de l'article, par souci de commodité pour les lecteurs ici.
Marcel
10

Lorsque vous transmettez un type de référence avec le mot clé ref, vous transmettez la référence par référence et la méthode que vous appelez peut attribuer une nouvelle valeur au paramètre. Ce changement se propagera à la portée appelante. Sans ref, la référence est passée par valeur, et cela ne se produit pas.

C # a aussi le mot-clé 'out' qui ressemble beaucoup à ref, sauf qu'avec 'ref', les arguments doivent être initialisés avant d'appeler la méthode, et avec 'out' vous devez affecter une valeur dans la méthode de réception.

Rytmis
la source
5

Il permet de modifier la référence transmise. Par exemple

void Bar()
{
    var y = new Foo();
    Baz(ref y);
}

void Baz(ref Foo y)
{
    y.Name = "2";

    // Overwrite the reference
    y = new Foo();
}

Vous pouvez également utiliser out si vous ne vous souciez pas de la référence transmise:

void Bar()
{
    var y = new Foo();
    Baz(out y);
}

void Baz(out Foo y)
{
    // Return a new reference
    y = new Foo();
}
Hans Van Slooten
la source
4

Un autre tas de code

class O
{
    public int prop = 0;
}

class Program
{
    static void Main(string[] args)
    {
        O o1 = new O();
        o1.prop = 1;

        O o2 = new O();
        o2.prop = 2;

        o1modifier(o1);
        o2modifier(ref o2);

        Console.WriteLine("1 : " + o1.prop.ToString());
        Console.WriteLine("2 : " + o2.prop.ToString());
        Console.ReadLine();
    }

    static void o1modifier(O o)
    {
        o = new O();
        o.prop = 3;
    }

    static void o2modifier(ref O o)
    {
        o = new O();
        o.prop = 4;
    }
}
Svend
la source
3

En plus des réponses existantes:

Comme vous avez demandé la différence entre les 2 méthodes: Il n'y a pas de co (ntra) variance lors de l'utilisation de refou out:

class Foo { }
class FooBar : Foo { }

static void Bar(Foo foo) { }
static void Bar(ref Foo foo) { foo = new Foo(); }

void Main()
{
    Foo foo = null;
    Bar(foo);           // OK
    Bar(ref foo);       // OK

    FooBar fooBar = null;
    Bar(fooBar);        // OK (covariance)
    Bar(ref fooBar);    // compile time error
}
élastique76
la source
1

Un paramètre dans une méthode semble toujours transmettre une copie, la question est une copie de quoi. Une copie est effectuée par un constructeur de copie pour un objet et comme toutes les variables sont des objets en C #, je pense que c'est le cas pour toutes. Les variables (objets) sont comme des personnes vivant à certaines adresses. Nous changeons les personnes vivant à ces adresses ou nous pouvons créer plus de références aux personnes vivant à ces adresses dans l'annuaire téléphonique (faire des copies superficielles). Ainsi, plus d'un identifiant peut faire référence à la même adresse. Les types de référence souhaitent plus d'espace, donc contrairement aux types de valeur qui sont directement connectés par une flèche à leur identifiant dans la pile, ils ont une valeur pour une autre adresse dans le tas (un plus grand espace pour habiter). Cet espace doit être extrait du tas.

Type de valeur: identificateur (contient valeur = adresse de la valeur de pile) ----> Valeur du type de valeur

Type de référence: Identificateur (contient valeur = adresse de la valeur de la pile) ----> (contient valeur = adresse de la valeur du tas) ----> Valeur du tas (contient le plus souvent des adresses vers d'autres valeurs), imaginez plus de flèches collées dans différentes directions vers Array [0], Array [1], array [2]

La seule façon de changer une valeur est de suivre les flèches. Si une flèche est perdue / modifiée de la manière dont la valeur est inaccessible.

Petar Drianov
la source
-1

Les variables de référence transportent l'adresse d'un endroit à un autre de sorte que toute mise à jour sur eux à n'importe quel endroit reflétera sur tous les endroits ALORS quelle est l'utilisation de REF. La variable de référence (405) est bonne jusqu'à ce qu'aucune nouvelle mémoire ne soit allouée à la variable de référence passée dans la méthode.

Une fois la nouvelle mémoire allouée (410) alors le changement de valeur sur cet objet (408) ne se reflétera pas partout. Pour cette référence vient. Ref est une référence de référence, donc chaque fois qu'une nouvelle mémoire allouée, elle le sait car elle pointe vers cet emplacement, la valeur peut donc être partagée par everyOne. Vous pouvez voir l'image pour plus de clarté.

Réf dans la variable de référence

srbh
la source