Qual è l'uso di "ref" per le variabili del tipo di riferimento in C #?


176

Capisco che se passo un valore-tipo ( int, structecc.) Come parametro (senza la refparola chiave), una copia di quella variabile viene passata al metodo, ma se uso la refparola chiave viene passato un riferimento a quella variabile, non uno nuovo.

Ma con tipi di riferimento, come le classi, anche senza la refparola chiave, viene passato un riferimento al metodo, non una copia. Quindi, a che serve la refparola chiave con i tipi di riferimento?


Prendi ad esempio:

var x = new Foo();

Qual è la differenza tra i seguenti?

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

e

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

Risposte:


154

È possibile modificare i foopunti da utilizzare y:

Foo foo = new Foo("1");

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

Bar(ref foo);
// foo.Name == "2"

17
quindi in pratica ottieni un riferimento al riferimento originale
lhahne,

2
Puoi cambiare a cosa si riferisce il riferimento originale, quindi sì.
user7116

1
Chris, la tua spiegazione è fantastica; Grazie per avermi aiutato a capire questo concetto.
Andreas Grech,

4
Quindi usare 'ref' su un oggetto è come usare i doppi puntatori in C ++?
Tom Hazel,

1
@TomHazel: -ish , a condizione che tu stia utilizzando i puntatori "doppi" in C ++ per cambiare ciò a cui punta un puntatore.
user7116,

29

Esistono casi in cui si desidera modificare il riferimento effettivo e non l'oggetto indicato:

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]);

21

Jon Skeet ha scritto un ottimo articolo sul passaggio dei parametri in C #. Descrive chiaramente il comportamento esatto e l'utilizzo dei parametri di passaggio per valore, riferimento ( ref) e output ( out).

Ecco una citazione importante da quella pagina in relazione ai refparametri:

I parametri di riferimento non passano i valori delle variabili utilizzate nell'invocazione del membro della funzione, ma usano le variabili stesse. Invece di creare una nuova posizione di memoria per la variabile nella dichiarazione del membro della funzione, viene utilizzata la stessa posizione di memoria, quindi il valore della variabile nel membro della funzione e il valore del parametro di riferimento saranno sempre gli stessi. I parametri di riferimento richiedono il modificatore di riferimento come parte sia della dichiarazione che dell'invocazione; ciò significa che è sempre chiaro quando si passa qualcosa per riferimento.


11
Mi piace l'analogia di passare il vostro guinzaglio i cani ad un amico per il passaggio di un riferimento per valore ... si rompe in fretta però, perché penso che sarebbe probabilmente avviso se il tuo amico scambiato-up lo shitzu a un doberman prima che ti riconsegna il guinzaglio ;-)
corlettk

16

Molto ben spiegato qui: http://msdn.microsoft.com/en-us/library/s6938f28.aspx

Estratto dall'articolo:

Una variabile di un tipo di riferimento non contiene direttamente i suoi dati; contiene un riferimento ai suoi dati. Quando si passa un parametro del tipo di riferimento per valore, è possibile modificare i dati a cui punta il riferimento, ad esempio il valore di un membro della classe. Tuttavia, non è possibile modificare il valore del riferimento stesso; cioè, non puoi usare lo stesso riferimento per allocare memoria per una nuova classe e farla persistere al di fuori del blocco. Per fare ciò, passa il parametro usando la parola chiave ref o out.


4
La spiegazione è davvero molto bella. Tuttavia, le risposte solo link sono scoraggiate su SO. Ho aggiunto un riassunto dell'articolo, per comodità dei lettori qui.
Marcel,

10

Quando si passa un tipo di riferimento con la parola chiave ref, si passa il riferimento per riferimento e il metodo chiamato può assegnare un nuovo valore al parametro. Tale modifica si propagherà all'ambito chiamante. Senza ref, il riferimento viene passato per valore e ciò non accade.

C # ha anche la parola chiave "out" che è molto simile a ref, tranne che con "ref", gli argomenti devono essere inizializzati prima di chiamare il metodo e con "out" è necessario assegnare un valore nel metodo di ricezione.


5

Ti permette di modificare il riferimento passato. Es

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

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

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

È inoltre possibile utilizzare fuori se non si preoccupano il riferimento passato in:

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

void Baz(out Foo y)
{
    // Return a new reference
    y = new Foo();
}

4

Un altro mazzo di codice

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

3

Oltre alle risposte esistenti:

Come richiesto per la differenza tra i 2 metodi: Non vi è alcuna varianza co (ntra) quando si utilizza refo 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
}

1

Un parametro in un metodo sembra passare sempre una copia, la domanda è una copia di cosa. Una copia viene eseguita da un costruttore di copie per un oggetto e poiché tutte le variabili sono Object in C #, credo che questo sia il caso per tutti loro. Le variabili (oggetti) sono come le persone che vivono in alcuni indirizzi. O cambiamo le persone che vivono a quegli indirizzi o possiamo creare più riferimenti alle persone che vivono a quegli indirizzi nella rubrica (fare copie superficiali). Pertanto, più di un identificatore può fare riferimento allo stesso indirizzo. I tipi di riferimento desiderano più spazio, quindi a differenza dei tipi di valore che sono collegati direttamente da una freccia al loro identificatore nello stack, hanno valore per un altro indirizzo nell'heap (uno spazio più grande da abitare). Questo spazio deve essere preso dal mucchio.

Tipo di valore: identificatore (contiene valore = indirizzo del valore dello stack) ----> Valore del tipo di valore

Tipo di riferimento: identificatore (contiene valore = indirizzo del valore dello stack) ----> (contiene valore = indirizzo del valore dell'heap) ----> valore dell'heap (il più delle volte contiene indirizzi ad altri valori), immagina più frecce che si attaccano in diversi indicazioni per Array [0], Array [1], array [2]

L'unico modo per modificare un valore è seguire le frecce. Se una freccia viene persa / modificata nel modo in cui il valore non è raggiungibile.


-1

Le variabili di riferimento trasportano l'indirizzo da un luogo a un altro, quindi qualsiasi aggiornamento su di essi in qualsiasi luogo si rifletterà su tutti i luoghi ALLORA a che serve REF. La variabile di riferimento (405) è valida fino a quando non viene allocata nuova memoria alla variabile di riferimento passata nel metodo.

Una volta allocata la nuova memoria (410), la modifica del valore su questo oggetto (408) non si rifletterà ovunque. Per questo riferimento arriva. Ref è riferimento di riferimento, quindi ogni volta che viene allocata una nuova memoria viene a sapere perché punta a quella posizione, quindi il valore può essere condiviso da everyOne. Puoi vedere l'immagine per maggiore chiarezza.

Rif. Nella variabile di riferimento

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.