2009-12-30 15 views
12

Capisco (o almeno credo di farlo) cosa significa passare un'istanza di una classe a un metodo di ref rispetto al non passaggio di ref. Quando o in quali circostanze si dovrebbe passare un'istanza di classe da ref? C'è una buona pratica quando si tratta di utilizzare la parola chiave ref per le istanze di classe?C# ref utilizzo della parola chiave

+0

Questo sarebbe utile se si dovesse mai invertire una lista collegata al di fuori di un'intervista su microsoft. void Reverse (ref Node Root) e quando si imposta Root, il Root del chiamante viene aggiornato. –

+0

Come ex programmatore di linguaggio assembly, vedo il tuo punto. – CiaoRoma

+1

Le istanze di classe vengono sempre passate per riferimento! (Mentre il riferimento viene passato per valore) L'unica ragione per usare ref è di manipolare il riferimento all'istanza di classe all'interno di un metodo. –

risposta

18

La spiegazione più chiara che abbia mai trovato per i parametri di output e ref è ... Jon Skeet's.

Parameter Passing in C#

Egli non va in "buone pratiche", ma se si capisce gli esempi che ha dato, potrai sapere quando è necessario usarli.

+0

Tutte le risposte sono molto utili; tuttavia, accetto la risposta di Womp in quanto riguarda specificamente la mia domanda sulle "migliori pratiche". Mille grazie a tutti! – CiaoRoma

+0

Non ho dubbi sul fatto che Jon Skeet lo spieghi perfettamente sul suo sito, ma questa risposta non è altro che un link e in quanto tale diventerà inutile quando il sito dietro il link andrà giù. – magnattic

2

La parola chiave ref consente di passare un argomento per riferimento. Per i tipi di riferimento, ciò significa che viene passato il riferimento effettivo a un oggetto (anziché una copia di tale riferimento). Per i tipi di valore questo significa che viene passato un riferimento alla variabile contenente il valore di quel tipo.

Viene utilizzato per i metodi che devono restituire più di un risultato ma non restituiscono un tipo complesso per incapsulare tali risultati. Permette di passare un riferimento a un oggetto nel metodo in modo che il metodo possa modificare quell'oggetto.

La cosa importante da ricordare è che i tipi di riferimento non vengono normalmente passati per riferimento, viene passata una copia di un riferimento. Ciò significa che non stai lavorando con il riferimento reale che ti è stato passato. Quando si utilizza ref in un'istanza di classe si passa il riferimento effettivo stesso in modo che tutte le modifiche ad esso (ad esempio impostandolo su null per esempio) verranno applicate al riferimento originale.

+0

Per restituire più di un valore è necessario utilizzare 'out'. –

+0

Che capisco, ma quando o in quali circostanze avrei bisogno o voglio usare la parola chiave ref per le istanze di classe? Questo è ciò che mi ha davvero sconcertato. – CiaoRoma

+0

Basta leggere la risposta di Shoham. Grazie per il vostro feedback – CiaoRoma

6

In modo succinto, si passa un valore come parametro ref se si desidera che la funzione che si sta chiamando sia in grado di modificare il valore di tale variabile.

Questo non equivale al passaggio di un riferimento tipo come parametro a una funzione. In questi casi, stai ancora passando per valore, ma il valore è un riferimento. In caso di passaggio per ref, viene inviato un riferimento effettivo alla variabile; in sostanza, tu e la funzione che chiami "condividi" la stessa variabile.

consideri il seguente:

public void Foo(ref int bar) 
{ 
    bar = 5; 
} 

... 

int baz = 2; 

Foo(ref baz); 

In questo caso, la variabile baz ha un valore di 5, quando è stato passato per riferimento. La semantica è completamente chiara per i tipi di valore, ma non è così chiara per i tipi di riferimento.

public class MyClass 
{ 
    public int PropName { get; set; } 
} 

public void Foo(MyClass bar) 
{ 
    bar.PropName = 5; 
} 

... 

MyClass baz = new MyClass(); 

baz.PropName = 2; 

Foo(baz); 

Come previsto, baz.PropName sarà 5, poiché MyClass è un tipo di riferimento. Ma facciamo questo:

public void Foo(MyClass bar) 
{ 
    bar = new MyClass(); 

    bar.PropName = 5; 
} 

Con lo stesso codice chiamante, baz.PropName rimarrà 2. Questo perché, anche se MyClass è un tipo di riferimento, Foo ha la sua variabile per bar; bar e baz iniziano appena con lo stesso valore, ma una volta Foo assegna un nuovo valore, sono solo due diverse variabili. Se, tuttavia, facciamo questo:

public void Foo(ref MyClass bar) 
{ 
    bar = new MyClass(); 

    bar.PropName = 5; 
} 

... 

MyClass baz = new MyClass(); 

baz.PropName = 2; 

Foo(ref baz); 

finiremo con PropName essere 5, dal momento che siamo passati baz per riferimento, rendendo le due funzioni "condividere" la stessa variabile.

+0

+1 Esempi eccellenti e chiari! – Nate

0

Quando si passano i tipi di riferimento (tipi non di valore) a un metodo, in entrambi i casi viene passato solo il riferimento.Ma quando si utilizza la parola chiave ref, il metodo chiamato può cambiare il riferimento.

Ad esempio:

public void MyMethod(ref MyClass obj) 
{ 
    obj = new MyClass(); 
} 

altrove:

MyClass x = y; // y is an instance of MyClass 

// x points to y 

MyMethod(ref x); 

// x points to a new instance of MyClass 

quando si chiama MyMethod (ref x), x punterà l'oggetto appena creato dopo la chiamata al metodo. x non punta più sull'oggetto originale.

+0

Questo esempio ha bisogno di un po 'di lavoro, dovresti usare invece la parola chiave out. –

+1

Questo esempio illustra semplicemente che il metodo chiamato può modificare il valore. Quando si utilizza la parola chiave out, il metodo chiamato non può utilizzare il parametro che è stato passato. Può solo impostarlo. –

11

Quando è possibile sostituire l'oggetto originale, è necessario inviarlo come ref. Se è solo per l'output e può essere non inizializzato prima di chiamare la funzione, verrà utilizzato out.

+0

+1 per menzionare può essere non inizializzato e il ref non può. –

0

La maggior parte dei casi di utilizzo per il passaggio di una variabile di riferimento per riferimento implica l'inizializzazione e l'uscita è più appropriata di ref. E compilano la stessa cosa (il compilatore applica diversi vincoli - che le variabili di riferimento siano inizializzate prima di essere passate e che le variabili siano inizializzate nel metodo). Quindi, l'unico caso in cui posso pensare a dove ciò sarebbe utile è dove è necessario fare un controllo di una variabile ref istanziata e potrebbe essere necessario reinizializzare in determinate circostanze.

Questo potrebbe anche essere necessario modificare una classe immutabile (come stringa) come sottolineato da Asaf R.

0

ho scoperto che è facile incorrere in problemi utilizzando la parola chiave ref.

Il seguente metodo modificherà f anche senza la parola chiave ref nella firma metodo perché f è un tipo di riferimento:

public void TrySet(Foo f,string s) 
{ 
    f.Bar = s; 
} 

In questo secondo caso, tuttavia, l'originale Foo è influenzato solo dalla prima linea di codice, il resto del metodo crea in qualche modo e influenza solo una nuova variabile locale.

public void TryNew(Foo f, string s) 
{ 
    f.Bar = ""; //original f is modified 
    f = new Foo(); //new f is created 
    f.Bar = s; //new f is modified, no effect on original f 
} 

Sarebbe bene se il compilatore ti ha dato un avvertimento in quel caso. Fondamentalmente quello che stai facendo è sostituire il riferimento che hai ricevuto con un altro che fa riferimento a un'area di memoria diversa.

E 'in realtà si vuole sostituire l'oggetto con una nuova istanza, utilizzare la parola chiave ref:

public void TryNew(ref Foo f, string s)... 

Ma Non sei riprese in un piede?Se il chiamante non è a conoscenza che un nuovo oggetto viene creato, il seguente codice probabilmente non funziona come previsto:

Foo f = SomeClass.AFoo; 
TryNew(ref f, "some string"); //this will clear SomeClass.AFoo.Bar and then create a new distinct object 

E se si tenta di "risolvere" il problema aggiungendo la riga:

SomeClass.AFoo = f; 

Se il codice contiene un riferimento a SomeClass.AFoo da qualche altra parte, che il riferimento non sarà più valido ...

Come regola generale, probabilmente si dovrebbe evitare di utilizzare la nuova parola chiave per modificare un oggetto che si legge da un'altra classe o ricevuto come parametro in un metodo.

Per quanto riguarda l'uso della parola chiave ref con i tipi di riferimento, posso suggerire questo approccio:

1) Non utilizzare se semplicemente impostando i valori del tipo di riferimento, ma essere esplicito nei nomi delle funzioni o dei parametri e nei commenti:

public void SetFoo(Foo fooToSet, string s) 
{ 
    fooToSet.Bar = s; 
} 

2) Quando c'è un motivo legittimo per sostituire il parametro di ingresso con una nuova, un'istanza diversa, utilizzare una funzione con un valore di ritorno invece:

public Foo TryNew(string s) 
{ 
    Foo f = new Foo(); 
    f.Bar = s; 
    return f; 
} 

Ma usando questa funzione potrebbe ancora avere conseguenze indesiderate con lo scenario SomeClass.AFoo:

SomeClass.AFoo = TryNew("some string");//stores a different object in SomeClass.AFoo 

3) In alcuni casi, come ad esempio la stringa di scambiare esempio here è comodo da usare params ref, ma proprio come nel caso 2 assicurati che lo scambio degli indirizzi degli oggetti non influenzi il resto del codice.

perché riesce allocazione di memoria per voi, C# rende fin troppo facile dimenticare tutto ciò che riguarda la gestione della memoria, ma in realtà aiuta a capire come puntatori e riferimenti funzionano. Altrimenti potresti introdurre bug sottili che sono difficili da trovare.

Infine, questo è il tipico caso in cui ci si vuole utilizzare un memcpy come la funzione, ma non v'è nulla di simile in C#, che io sappia.

Problemi correlati