Perché usare la parola chiave 'ref' quando si passa un oggetto?


290

Se sto passando un oggetto a un metodo, perché dovrei usare la parola chiave ref? Non è comunque questo il comportamento predefinito?

Per esempio:

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

L'output è "Bar", il che significa che l'oggetto è stato passato come riferimento.

Risposte:


298

Passa a refse vuoi cambiare l'oggetto:

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

Dopo aver chiamato DoSomething, tnon si riferisce all'originale new TestRef, ma si riferisce a un oggetto completamente diverso.

Ciò può essere utile anche se si desidera modificare il valore di un oggetto immutabile, ad es string. A. Non è possibile modificare il valore di a una stringvolta che è stato creato. Ma utilizzando a ref, è possibile creare una funzione che modifica la stringa per un'altra che ha un valore diverso.

Modifica: come altri hanno già detto. Non è una buona idea usare a refmeno che non sia necessario. L'utilizzo refdà al metodo la libertà di modificare l'argomento per qualcos'altro, i chiamanti del metodo dovranno essere codificati per assicurarsi di gestire questa possibilità.

Inoltre, quando il tipo di parametro è un oggetto, le variabili oggetto fungono sempre da riferimenti all'oggetto. Ciò significa che quando refviene utilizzata la parola chiave, si ottiene un riferimento a un riferimento. Ciò ti consente di fare le cose come descritto nell'esempio sopra riportato. Ma, quando il tipo di parametro è un valore primitivo (ad es. int), Quindi se questo parametro è assegnato all'interno del metodo, il valore dell'argomento passato verrà modificato dopo che il metodo restituisce:

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

88

È necessario distinguere tra "passaggio di un riferimento per valore" e "passaggio di un parametro / argomento per riferimento".

Ho scritto un articolo abbastanza lungo sull'argomento per evitare di dover scrivere con attenzione ogni volta che si presenta sui newsgroup :)


1
Bene, ho riscontrato il problema durante l'aggiornamento di VB6 nel codice .Net C #. Esistono firme di funzioni / metodi che accettano parametri ref, out e plain. Quindi, come possiamo distinguere meglio la differenza tra un parametro semplice rispetto a un riferimento?
bonCodigo

2
@bonCodigo: Non sei sicuro di cosa intendi per "distinguere meglio": fa parte della firma e devi specificare anche refsul sito della chiamata ... dove altro vorresti che fosse distinto? Anche la semantica è ragionevolmente chiara, ma deve essere espressa con attenzione (piuttosto che "gli oggetti vengono passati per riferimento" che è la semplificazione eccessiva comune).
Jon Skeet,

non so perché Visual Studio non mostri ancora esplicitamente ciò che viene passato
MonsterMMORPG

3
@MonsterMMORPG: Non so cosa intendi con questo, temo.
Jon Skeet,

56

In .NET quando si passa qualsiasi parametro a un metodo, viene creata una copia. Nei tipi di valore significa che qualsiasi modifica apportata al valore rientra nell'ambito del metodo e va persa quando si esce dal metodo.

Quando si passa un tipo di riferimento, viene anche creata una copia, ma è una copia di un riferimento, ovvero ora si hanno DUE riferimenti in memoria per lo stesso oggetto. Pertanto, se si utilizza il riferimento per modificare l'oggetto, viene modificato. Ma se modifichi il riferimento stesso - dobbiamo ricordare che è una copia - anche eventuali modifiche andranno perse all'uscita dal metodo.

Come si è già detto, un compito è una modifica del riferimento, quindi si perde:

public void Method1(object obj) {   
 obj = new Object(); 
}

public void Method2(object obj) {  
 obj = _privateObject; 
}

I metodi sopra non modificano l'oggetto originale.

Una piccola modifica del tuo esempio

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

6
Mi piace questa risposta meglio della risposta accettata. Spiega più chiaramente cosa succede quando si passa una variabile del tipo di riferimento con la parola chiave ref. Grazie!
Stefan,

17

Poiché TestRef è una classe (che sono oggetti di riferimento), è possibile modificare il contenuto all'interno di t senza passarlo come riferimento. Tuttavia, se si passa t come riferimento, TestRef può modificare ciò a cui si riferisce la t originale. vale a dire farlo puntare a un oggetto diverso.


16

Con refte puoi scrivere:

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

E t verrà modificato dopo il completamento del metodo.


8

Pensa alle variabili (ad es. foo) Dei tipi di riferimento (ad es. List<T>) Come a contenere identificatori di oggetti nella forma "Oggetto # 24601". Supponiamo che l'istruzione foo = new List<int> {1,5,7,9};induca fooa contenere "Oggetto # 24601" (un elenco con quattro elementi). Quindi la chiamata foo.Lengthrichiederà l'oggetto # 24601 per la sua lunghezza e risponderà 4, quindi foo.Lengthsarà uguale a 4.

Se fooviene passato a un metodo senza utilizzarlo ref, tale metodo potrebbe apportare modifiche all'oggetto # 24601. Come conseguenza di tali cambiamenti, foo.Lengthpotrebbe non essere più uguale a 4. Il metodo stesso, tuttavia, non sarà in grado di cambiare foo, che continuerà a contenere "Oggetto # 24601".

Il passaggio foocome refparametro consentirà al metodo chiamato di apportare modifiche non solo all'oggetto # 24601, ma anche a foose stesso. Il metodo potrebbe creare un nuovo oggetto # 8675309 e memorizzare un riferimento a quello in foo. In tal caso, foonon contenere più "Oggetto # 24601", ma "Oggetto # 8675309".

In pratica, le variabili del tipo di riferimento non contengono stringhe del modulo "Oggetto # 8675309"; non contengono nemmeno nulla che possa essere significativamente convertito in un numero. Anche se ogni variabile del tipo di riferimento conterrà alcuni pattern di bit, non esiste una relazione fissa tra i pattern di bit memorizzati in tali variabili e gli oggetti che identificano. Non è possibile in alcun modo che il codice possa estrarre informazioni da un oggetto o un riferimento ad esso, e in seguito determinare se un altro riferimento ha identificato lo stesso oggetto, a meno che il codice non contenesse o conoscesse un riferimento che identificava l'oggetto originale.


5

Questo è come passare un puntatore a un puntatore in C. In .NET questo vi permetterà di cambiare ciò che il T originale si riferisce a, personalmente anche se credo che se si sta facendo che in .NET probabilmente avete ottenuto un problema di progettazione!


3

Utilizzando la refparola chiave con i tipi di riferimento, si passa effettivamente un riferimento al riferimento. In molti modi è come usare la outparola chiave, ma con la minima differenza che non esiste alcuna garanzia che il metodo assegnerà effettivamente qualsiasi cosa al refparametro 'ed.


3

ref imita (o si comporta) come un'area globale solo per due ambiti:

  • visitatore
  • Chiamato.

1

Se stai passando un valore, tuttavia, le cose sono diverse. È possibile forzare il passaggio di un valore per riferimento. Ciò consente, ad esempio, di passare un numero intero a un metodo e di modificare il numero intero per conto dell'utente.


4
Che si stia passando un riferimento o un valore di tipo valore, il comportamento predefinito è passare per valore. Devi solo capire che con i tipi di riferimento, il valore che stai passando è un riferimento. Questo non è lo stesso del passaggio per riferimento.
Jon Skeet,

1

Ref indica se la funzione può mettere le mani sull'oggetto stesso o solo sul suo valore.

Il passaggio per riferimento non è vincolato a una lingua; è una strategia di associazione dei parametri accanto al valore per passaggio, passaggio per nome, passaggio per necessità, ecc ...

Un sidenote: il nome della classe TestRefè una scelta orribilmente negativa in questo contesto;).

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.