Quando utilizzare in vs ref vs out


383

Qualcuno mi ha chiesto l'altro giorno quando dovrebbero usare la parola chiave parametro outinvece di ref. Mentre io (penso) capisco la differenza tra le parole chiave refe out(che è stato chiesto in precedenza ) e la migliore spiegazione sembra essere quella ref== ineout , quali sono alcuni esempi (ipotetici o di codice) in cui dovrei sempre usare oute non ref.

Dal momento che refè più generale, perché mai vuoi usare out? È solo zucchero sintattico?


18
Una variabile passata usando outnon può essere letta prima che sia assegnata a. refnon ha questa limitazione. Quindi c'è quello.
Corey Ogburn,

17
In breve, refè per in / out, mentre outè un parametro out-only.
Tim S.

3
Cosa non ottieni esattamente?
dal

4
Anche le outvariabili DEVONO essere assegnate nella funzione.
Corey Ogburn,

Grazie Corey. Ma non l'ho già fatto. Il mio punto è che qual è il vantaggio di questo. In realtà ho bisogno di un esempio che mostri uno scenario in cui possiamo usare il parametro ref per ottenere una funzionalità che non può essere raggiunta usando il parametro out e viceversa.
Rajbir Singh,

Risposte:


399

Dovresti usare a outmeno che non sia necessario ref.

Fa una grande differenza quando è necessario eseguire il marshalling dei dati, ad esempio in un altro processo, che può essere costoso. Pertanto, si desidera evitare il marshalling del valore iniziale quando il metodo non lo utilizza.

Oltre a ciò, mostra anche al lettore della dichiarazione o della chiamata se il valore iniziale è rilevante (e potenzialmente conservato), o gettato via.

Come differenza minore, non è necessario inizializzare un parametro out.

Esempio per out:

string a, b;
person.GetBothNames(out a, out b);

dove GetBothNames è un metodo per recuperare atomicamente due valori, il metodo non cambierà il comportamento qualunque sia aeb. Se la chiamata viene inviata a un server alle Hawaii, la copia dei valori iniziali da qui alle Hawaii è uno spreco di larghezza di banda. Uno snippet simile usando ref:

string a = String.Empty, b = String.Empty;
person.GetBothNames(ref a, ref b);

potrebbe confondere i lettori, perché sembra che i valori iniziali di aeb siano rilevanti (sebbene il nome del metodo indichi che non lo sono).

Esempio per ref:

string name = textbox.Text;
bool didModify = validator.SuggestValidName(ref name);

Qui il valore iniziale è rilevante per il metodo.


5
"Non è proprio così." - puoi spiegare meglio cosa intendi?
Peter,

3
Non si desidera utilizzare refper i valori predefiniti.
C.Evenhuis,

155
Per i posteri: un'altra differenza che nessun altro sembra aver menzionato, come affermato qui ; per un outparametro, è necessario che il metodo chiamante assegni un valore prima che il metodo ritorni. - non si deve fare nulla con un parametro ref.
brichins,

3
@brichins Fare riferimento alla sezione "commenti (aggiunte alla community)" nel collegamento indicato dall'utente. È un errore corretto nella documentazione di VS 2008.
Bharat Ram V

13
@brichins è richiesto il metodo chiamato per assegnare un valore, non il metodo chiamante. zverev.eugene questo è ciò che è stato corretto nella documentazione VS 2008.
Segfault,

72

Utilizzare per indicare che il parametro non viene utilizzato, ma solo impostato. Questo aiuta il chiamante a capire che stai sempre inizializzando il parametro.

Inoltre, ref e out non sono solo per tipi di valore. Consentono inoltre di ripristinare l'oggetto a cui fa riferimento un tipo di riferimento all'interno di un metodo.


3
+1 Non sapevo che potesse essere utilizzato anche per tipi di riferimento, bella risposta chiara, grazie
Dale

@brichins: No, non puoi. outi parametri vengono trattati come non assegnati all'ingresso della funzione. Non sarai in grado di ispezionarne il valore fino a quando non avrai assegnato per la prima volta un certo valore: non è possibile utilizzare il valore del parametro al momento della chiamata della funzione.
Ben Voigt,

È vero, non è possibile accedere al valore prima di un'assegnazione interna. Mi riferivo al fatto che il parametro stesso può essere usato più avanti nel metodo - non è bloccato. Se questo debba effettivamente essere fatto o meno è una discussione diversa (sul design); Volevo solo sottolineare che era possibile. Grazie per il chiarimento.
brichins,

2
@ ดาว: può essere utilizzato con i tipi di riferimento perché quando si passa un parametro del tipo di riferimento, ciò che si passa è il valore del riferimento e non l'oggetto stesso. Quindi è ancora pass-by-value.
Tarik,

38

Hai ragione nel dire che semanticamente reffornisce sia la funzionalità "in" che "out", mentre outfornisce solo la funzionalità "out". Ci sono alcune cose da considerare:

  1. outrichiede che il metodo che accetta il parametro DEVE, ad un certo punto prima di ritornare, assegnare un valore alla variabile. Questo modello si trova in alcune classi di archiviazione dei dati chiave / valore come Dictionary<K,V>, in cui sono presenti funzioni come TryGetValue. Questa funzione accetta un outparametro che contiene quale sarà il valore se recuperato. Non avrebbe senso per il chiamante passare un valore in questa funzione, quindi outviene utilizzato per garantire che un certo valore sarà nella variabile dopo la chiamata, anche se non sono dati "reali" (nel caso diTryGetValue dove la chiave non è presente).
  2. oute i refparametri sono sottoposti a marshalling in modo diverso quando si tratta di codice di interoperabilità

Inoltre, a parte, è importante notare che mentre i tipi di riferimento e i tipi di valore differiscono nella natura del loro valore, ogni variabile nell'applicazione punta a una posizione di memoria che contiene un valore , anche per i tipi di riferimento. Accade semplicemente che, con i tipi di riferimento, sia il valore contenuto in quella posizione di memoria un altroposizione di memoria. Quando si passano i valori a una funzione (o si esegue qualsiasi altra assegnazione di variabili), il valore di quella variabile viene copiato nell'altra variabile. Per i tipi di valore, ciò significa che viene copiato l'intero contenuto del tipo. Per i tipi di riferimento, ciò significa che viene copiata la posizione della memoria. In entrambi i casi, crea una copia dei dati contenuti nella variabile. L'unica reale rilevanza che questo ha a che fare riguarda la semantica degli incarichi; quando si assegna una variabile o si passa per valore (impostazione predefinita), quando viene effettuata una nuova assegnazione alla variabile originale (o nuova), non influisce sull'altra variabile. Nel caso di tipi di riferimento, sì, sono state apportate modifiche all'istanzasono disponibili su entrambi i lati, ma questo perché la variabile effettiva è solo un puntatore a un'altra posizione di memoria; il contenuto della variabile - la posizione della memoria - non è cambiato in realtà.

Il passaggio con la refparola chiave indica che sia la variabile originale sia il parametro della funzione indicheranno effettivamente la stessa posizione di memoria. Questo, ancora una volta, influisce solo sulla semantica del compito. Se un nuovo valore viene assegnato a una delle variabili, poiché poiché l'altro punta nella stessa posizione di memoria, il nuovo valore si rifletterà sull'altro lato.


1
Si noti che il requisito che il metodo chiamato assegna un valore a un parametro out è applicato dal compilatore c # e non dall'IL sottostante. Pertanto, una libreria scritta in VB.NET potrebbe non essere conforme a tale convenzione.
jmoreno

Sembra che il ref sia in realtà l'equivalente del simbolo di dereferenziazione in C ++ (*). Il riferimento del passby in C # deve essere equivalente a quello che C / C ++ fa riferimento a doppio puntatore (puntatore a un puntatore), quindi ref deve fare la differenza al primo puntatore, consentendo al metodo chiamato di accedere alla posizione di memoria dell'oggetto reale nel contesto.
Vieni il

In realtà suggerirei un corretto TryGetValuesarebbe usare refe non outesplicitamente nel caso di non trovare la chiave.
NetMage

27

Dipende dal contesto di compilazione (vedi esempio sotto).

outed refentrambi indicano il passaggio della variabile per riferimento, tuttavia refrichiede l'inizializzazione della variabile prima del passaggio, il che può rappresentare una differenza importante nel contesto del marshalling (Interop: UmanagedToManagedTransition o viceversa)

MSDN avverte :

Non confondere il concetto di passaggio per riferimento con il concetto di tipi di riferimento. I due concetti non sono gli stessi. Un parametro di metodo può essere modificato da ref indipendentemente dal fatto che si tratti di un tipo di valore o di un tipo di riferimento. Non esiste un boxing di un tipo di valore quando viene passato per riferimento.

Dai documenti MSDN ufficiali:

La parola chiave out fa passare argomenti per riferimento. Questo è simile alla parola chiave ref, tranne per il fatto che ref richiede l'inizializzazione della variabile prima di essere passata

La parola chiave ref fa passare un argomento per riferimento, non per valore. L'effetto del passaggio per riferimento è che qualsiasi modifica al parametro nel metodo si riflette nella variabile argomento sottostante nel metodo chiamante. Il valore di un parametro di riferimento è sempre uguale al valore della variabile argomento sottostante.

Possiamo verificare che out e ref siano effettivamente gli stessi quando viene assegnato l'argomento:

Esempio CIL :

Considera il seguente esempio

static class outRefTest{
    public static int myfunc(int x){x=0; return x; }
    public static void myfuncOut(out int x){x=0;}
    public static void myfuncRef(ref int x){x=0;}
    public static void myfuncRefEmpty(ref int x){}
    // Define other methods and classes here
}

in CIL, le istruzioni myfuncOute myfuncRefsono identiche a quelle previste.

outRefTest.myfunc:
IL_0000:  nop         
IL_0001:  ldc.i4.0    
IL_0002:  starg.s     00 
IL_0004:  ldarg.0     
IL_0005:  stloc.0     
IL_0006:  br.s        IL_0008
IL_0008:  ldloc.0     
IL_0009:  ret         

outRefTest.myfuncOut:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldc.i4.0    
IL_0003:  stind.i4    
IL_0004:  ret         

outRefTest.myfuncRef:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldc.i4.0    
IL_0003:  stind.i4    
IL_0004:  ret         

outRefTest.myfuncRefEmpty:
IL_0000:  nop         
IL_0001:  ret         

nop : nessuna operazione, ldloc : carica locale, stloc : impila locale, ldarg : carica argomento, bs.s : branch to target ....

(Vedi: Elenco delle istruzioni CIL )


23

Di seguito sono riportate alcune note che ho tratto da questo articolo codeproject su C # Out Vs Ref

  1. Dovrebbe essere usato solo quando ci aspettiamo più output da una funzione o un metodo. Un pensiero sulle strutture può anche essere una buona opzione per lo stesso.
  2. REF e OUT sono parole chiave che determinano il modo in cui i dati vengono trasmessi dal chiamante alla chiamata e viceversa.
  3. In REF i dati passano a due vie. Dal chiamante alla chiamata e viceversa.
  4. In Out i dati passano solo in un modo dalla chiamata al chiamante. In questo caso, se Caller ha tentato di inviare dati al chiamante, questi verranno ignorati / rifiutati.

Se sei una persona visiva, ti preghiamo di vedere questo video di Yourtube che dimostra praticamente la differenza https://www.youtube.com/watch?v=lYdcY5zulXA

L'immagine sotto mostra le differenze visivamente

C # Out Vs Ref


1
one-way, i two-waytermini potrebbero essere utilizzati in modo improprio qui. In realtà sono entrambi bidirezionali, tuttavia i loro comportamenti concettuali differiscono in termini di riferimenti e valori dei parametri
ibubi,

17

È necessario utilizzare refse si prevede di leggere e scrivere nel parametro. È necessario utilizzare outse si prevede solo di scrivere. In effetti,out è per quando avresti bisogno di più di un valore di ritorno, o quando non vuoi usare il normale meccanismo di ritorno per l'output (ma questo dovrebbe essere raro).

Esistono meccanici linguistici che assistono questi casi d'uso. Refi parametri devono essere stati inizializzati prima di essere passati a un metodo (ponendo l'accento sul fatto che sono di lettura-scrittura) e i outparametri non possono essere letti prima che gli venga assegnato un valore e si garantisce che siano stati scritti alla fine di il metodo (ponendo l'accento sul fatto che sono solo di scrittura). Contravvenire a questi principi provoca un errore in fase di compilazione.

int x;
Foo(ref x); // error: x is uninitialized

void Bar(out int x) {}  // error: x was not written to

Ad esempio, int.TryParserestituisce a boole accetta un out intparametro:

int value;
if (int.TryParse(numericString, out value))
{
    /* numericString was parsed into value, now do stuff */
}
else
{
    /* numericString couldn't be parsed */
}

Questo è un chiaro esempio di una situazione in cui è necessario generare due valori: il risultato numerico e se la conversione ha avuto esito positivo o meno. Gli autori del CLR hanno deciso di optare per outquesto dato che non si preoccupano di qualeint avrebbe potuto essere prima.

Per ref, puoi guardare Interlocked.Increment:

int x = 4;
Interlocked.Increment(ref x);

Interlocked.Incrementincrementa atomicamente il valore di x. Poiché è necessario leggere xper incrementarlo, questa è una situazione in cui refè più appropriato. Ti preoccupi totalmente di ciò che xera prima che fosse passato Increment.

Nella prossima versione di C #, sarà persino possibile dichiarare variabili nei outparametri, aggiungendo ancora più enfasi alla loro natura di solo output:

if (int.TryParse(numericString, out int value))
{
    // 'value' exists and was declared in the `if` statement
}
else
{
    // conversion didn't work, 'value' doesn't exist here
}

Grazie zneak per la tua risposta. Ma puoi spiegarmi perché non ho potuto usarlo per leggere e scrivere un parametro?
Rajbir Singh,

@RajbirSingh, poiché i outparametri non sono stati necessariamente inizializzati, quindi il compilatore non ti permetterà di leggere da un outparametro fino a quando non avrai scritto qualcosa su di esso.
zneak,

zneak, sono d'accordo con te. Ma nell'esempio seguente un parametro out può essere usato come read e write: string name = "myName"; private void OutMethod (out string nameOut) {if (nameOut == "myName") {nameOut = "Metodo Rajbir Singh in out"; }}
Rajbir Singh,

1
@RajbirSingh, il tuo esempio non viene compilato. Non puoi leggere nameOutnella tua ifdichiarazione perché non è stato assegnato nulla prima.
zneak,

Grazie @zneak. Hai assolutamente ragione. Non si compila. Grazie mille per il mio aiuto e ora ha senso per me :)
Rajbir Singh, il

7

outè la versione con più vincoli di ref.

In un corpo del metodo, è necessario assegnare a tutti i outparametri prima di abbandonare il metodo. Inoltre, i valori assegnati a un outparametro vengono ignorati, mentre è refnecessario assegnarli.

Quindi outti permette di fare:

int a, b, c = foo(out a, out b);

dove refrichiederebbe l'assegnazione di aeb.


Semmai, outè la versione meno vincolata. refha "Presupposto: variabile è sicuramente assegnato, Post-condizione: variabile viene definitivamente assegnato", mentre outha solo `Post-condizione:. variabile è sicuramente assegnata" (E come previsto, più è richiesto di un'implementazione funzione con un minor numero di condizioni preliminari)
Ben Voigt

@BenVoigt: Suppongo che dipenda da quale direzione stai guardando :) Penso che volevo dire vincolo in termini di flessibilità di codifica (?).
Leppie,

7

Come suona:

fuori = solo di inizializzazione / riempire un parametro (il parametro deve essere vuoto) restituirlo fuori pianura

ref = riferimento, parametri standard (magari con il valore), ma la funzione può modifiy esso.


La variabile parametro out può ottenere un valore prima di passarlo a un metodo.
Bence Végert,

6

È possibile utilizzare la outparola chiave contestuale in due contesti (ognuno è un collegamento a informazioni dettagliate), come modificatore di parametri o nelle dichiarazioni di parametri di tipo generico in interfacce e delegati. In questo argomento viene descritto il modificatore di parametro, ma è possibile vedere questo altro argomento per informazioni sulle dichiarazioni di parametri di tipo generico.

La outparola chiave fa passare argomenti per riferimento. È come la refparola chiave, tranne per il fatto che è refnecessario inizializzare la variabile prima di passarla. Per utilizzare un outparametro, sia la definizione del metodo sia il metodo chiamante devono utilizzare esplicitamente la outparola chiave. Ad esempio: C #

class OutExample
{
    static void Method(out int i)
    {
        i = 44;
    }
    static void Main()
    {
        int value;
        Method(out value);
        // value is now 44
    }
}

Sebbene le variabili passate come outargomenti non debbano essere inizializzate prima di essere passate, il metodo chiamato deve assegnare un valore prima che il metodo ritorni.

Sebbene le parole chiave refe outcausino un comportamento di runtime diverso, non sono considerate parte della firma del metodo al momento della compilazione. Pertanto, i metodi non possono essere sovraccaricati se l'unica differenza è che un metodo accetta un refargomento e l'altro accetta un outargomento. Il seguente codice, ad esempio, non verrà compilato: C #

class CS0663_Example
{
    // Compiler error CS0663: "Cannot define overloaded 
    // methods that differ only on ref and out".
    public void SampleMethod(out int i) { }
    public void SampleMethod(ref int i) { }
}

Il sovraccarico può essere eseguito, tuttavia, se un metodo accetta a refo outargomento e l'altro non ne utilizza nessuno, in questo modo: C #

class OutOverloadExample
{
    public void SampleMethod(int i) { }
    public void SampleMethod(out int i) { i = 5; }
}

Le proprietà non sono variabili e pertanto non possono essere passate come outparametri.

Per informazioni sul passaggio di matrici, consultare Passaggio di matrici mediante refe out(Guida per programmatori C #).

Non è possibile utilizzare le parole chiave refe outper i seguenti tipi di metodi:

Async methods, which you define by using the async modifier.

Iterator methods, which include a yield return or yield break statement.

Esempio

La dichiarazione di un outmetodo è utile quando si desidera che un metodo restituisca più valori. L'esempio seguente utilizza outper restituire tre variabili con una singola chiamata di metodo. Si noti che il terzo argomento è assegnato a null. Ciò consente ai metodi di restituire valori facoltativamente. C #

class OutReturnExample
{
    static void Method(out int i, out string s1, out string s2)
    {
        i = 44;
        s1 = "I've been returned";
        s2 = null;
    }
    static void Main()
    {
        int value;
        string str1, str2;
        Method(out value, out str1, out str2);
        // value is now 44
        // str1 is now "I've been returned"
        // str2 is (still) null;
    }
}

6

Come usare ino outo refin C #?

  • Tutte le parole chiave C#hanno la stessa funzionalità ma con alcuni limiti .
  • in gli argomenti non possono essere modificati con il metodo chiamato.
  • ref gli argomenti possono essere modificati.
  • ref deve essere inizializzato prima di essere utilizzato dal chiamante, può essere letto e aggiornato nel metodo.
  • out gli argomenti devono essere modificati dal chiamante.
  • out gli argomenti devono essere inizializzati nel metodo
  • Le variabili passate come inargomenti devono essere inizializzate prima di essere passate in una chiamata di metodo. Tuttavia, il metodo chiamato potrebbe non assegnare un valore o modificare l'argomento.

Non è possibile utilizzare i in, refe outle parole chiave per i seguenti tipi di metodi:

  • Metodi asincroni , definiti mediante il asyncmodificatore.
  • Metodi di iteratore , che includono un'istruzione yield returno yield break.

5

Giusto per chiarire sul commento di OP che l'uso su ref e out è un "riferimento a un tipo di valore o struttura dichiarata al di fuori del metodo", che è già stato stabilito in modo errato.

Considera l'uso di ref su StringBuilder, che è un tipo di riferimento:

private void Nullify(StringBuilder sb, string message)
{
    sb.Append(message);
    sb = null;
}

// -- snip --

StringBuilder sb = new StringBuilder();
string message = "Hi Guy";
Nullify(sb, message);
System.Console.WriteLine(sb.ToString());

// Output
// Hi Guy

Come allegato a questo:

private void Nullify(ref StringBuilder sb, string message)
{
    sb.Append(message);
    sb = null;
}

// -- snip --

StringBuilder sb = new StringBuilder();
string message = "Hi Guy";
Nullify(ref sb, message);
System.Console.WriteLine(sb.ToString());

// Output
// NullReferenceException

4

Un argomento passato come ref deve essere inizializzato prima di passare al metodo, mentre il parametro out non deve essere inizializzato prima di passare a un metodo.


4

perché mai vuoi usarlo?

Per far sapere agli altri che la variabile verrà inizializzata quando ritorna dal metodo chiamato!

Come accennato in precedenza: "per un parametro out, il metodo chiamante è necessario per assegnare un valore prima che il metodo ritorni ."

esempio:

Car car;
SetUpCar(out car);
car.drive();  // You know car is initialized.

4

Fondamentalmente entrambi refe outper passare oggetto / valore tra metodi

La parola chiave out fa passare argomenti per riferimento. È come la parola chiave ref, tranne per il fatto che ref richiede l'inizializzazione della variabile prima che venga passata.

out : L'argomento non è inizializzato e deve essere inizializzato nel metodo

ref : L'argomento è già inizializzato e può essere letto e aggiornato nel metodo.

A che cosa serve "ref" per i tipi di riferimento?

È possibile modificare il riferimento dato in un'altra istanza.

Lo sapevate?

  1. Sebbene le parole chiave ref e out causino comportamenti di runtime diversi, non sono considerate parte della firma del metodo al momento della compilazione. Pertanto, i metodi non possono essere sovraccaricati se l'unica differenza è che un metodo accetta un argomento ref e l'altro accetta un argomento out.

  2. Non è possibile utilizzare le parole chiave ref e out per i seguenti tipi di metodi:

    • Metodi asincroni, definiti mediante il modificatore asincrono.
    • Metodi di iteratore, che includono una dichiarazione di rendimento o di rendimento.
  3. Le proprietà non sono variabili e pertanto non possono essere passate come parametri out.


4

Note extra su C # 7:
In C # 7 non è necessario dichiarare le variabili usando out. Quindi un codice come questo:

public void PrintCoordinates(Point p)
{
  int x, y; // have to "predeclare"
  p.GetCoordinates(out x, out y);
  WriteLine($"({x}, {y})");
}

Può essere scritto così:

public void PrintCoordinates(Point p)
{
  p.GetCoordinates(out int x, out int y);
  WriteLine($"({x}, {y})");
}

Fonte: Novità di C # 7.


4

Sento ancora la necessità di un buon riassunto, questo è quello che mi è venuto in mente.

Sommario,

Quando siamo all'interno della funzione , ecco come specifichiamo il controllo di accesso ai dati variabili ,

in = R

out = deve W prima di R

ref = R + W


Spiegazione,

in

La funzione può LEGGERE solo quella variabile.

out

La variabile non deve essere inizializzata prima perché la
funzione DEVE SCRIVERE prima di LEGGERE .

ref

La funzione può LEGGERE / SCRIVERE su quella variabile.


Perché è chiamato come tale?

Concentrandosi su dove i dati vengono modificati,

in

I dati devono essere impostati solo prima di accedere alla funzione (in).

out

I dati devono essere impostati solo prima di uscire (fuori).

ref

I dati devono essere impostati prima di accedere alla funzione (in).
I dati possono essere impostati prima di uscire dalla funzione (fuori).


forse (in / out / ref) dovrebbe essere rinominato in (r / wr / rw). o forse no, in / out è una metafora più bella.
Armeggi il

0

Va notato che inè una parola chiave valida dal C # ver 7.2 :

Il modificatore di parametri in è disponibile in C # 7.2 e versioni successive. Le versioni precedenti generano l'errore del compilatore CS8107 ("La funzione 'riferimenti di sola lettura' non è disponibile in C # 7.0. Utilizzare la versione 7.2 o successiva della lingua.") Per configurare la versione della lingua del compilatore, vedere Selezionare la versione della lingua C #.

...

La parola chiave in fa sì che gli argomenti vengano passati per riferimento. Rende il parametro formale un alias per l'argomento, che deve essere una variabile. In altre parole, qualsiasi operazione sul parametro viene eseguita sull'argomento. È come le parole chiave ref o out, tranne che negli argomenti non possono essere modificati dal metodo chiamato. Mentre gli argomenti ref possono essere modificati, gli argomenti out devono essere modificati con il metodo chiamato e tali modifiche sono osservabili nel contesto chiamante.

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.