Perché C # non supporta la restituzione di riferimenti?


141

Ho letto che .NET supporta la restituzione di riferimenti, ma C # no. C'è un motivo speciale? Perché non riesco a fare qualcosa del genere:

static ref int Max(ref int x, ref int y) 
{ 
  if (x > y) 
    return ref x; 
  else 
    return ref y; 
} 

8
um ... citazione per favore?
RPM1984,

5
C # ha valore e tipi di riferimento ed entrambi possono essere restituiti cosa intendi.
Riesegui il

Sto parlando di qualcosa del tiporeturn ref x
Tom Sarduy,

2
Dopo 4 anni, puoi fare esattamente questo con C# 7:)
Arghya C

Risposte:


188

Questa domanda è stata l'oggetto del mio blog il 23 giugno 2011 . Grazie per l'ottima domanda!

Il team di C # lo sta prendendo in considerazione per C # 7. Vedi https://github.com/dotnet/roslyn/issues/5233 maggiori dettagli, .

AGGIORNAMENTO: la funzionalità è arrivata a C # 7!


Hai ragione; .NET supporta metodi che restituiscono riferimenti gestiti alle variabili. .NET supporta anche variabili locali che contengono riferimenti gestiti ad altre variabili. (Si noti tuttavia che .NET non supporta campi o array che contengono riferimenti gestiti ad altre variabili perché ciò complica eccessivamente la storia della garbage collection. Inoltre, i tipi di "riferimento gestito a variabile" non sono convertibili in oggetto e pertanto non possono essere utilizzati come digita argomenti su tipi o metodi generici.)

Il commentatore "RPM1984" per qualche motivo ha chiesto una citazione per questo fatto. RPM1984 Vi incoraggio a leggere la specifica CLI Partizione I Sezione 8.2.1.1, "Puntatori gestiti e tipi correlati" per informazioni su questa funzionalità di .NET.

È del tutto possibile creare una versione di C # che supporti entrambe queste funzionalità. Potresti quindi fare cose del genere

static ref int Max(ref int x, ref int y) 
{ 
  if (x > y) 
    return ref x; 
  else 
    return ref y; 
} 

e poi chiamalo con

int a = 123;
int b = 456; 
ref int c = ref Max(ref a, ref b); 
c += 100;
Console.WriteLine(b); // 556!

So empiricamente che è possibile creare una versione di C # che supporti queste funzionalità perché l'ho fatto . I programmatori avanzati, in particolare le persone che eseguono il porting di codice C ++ non gestito, spesso ci chiedono una maggiore capacità simile al C ++ di fare le cose con riferimenti senza dover uscire dal grosso martello di usare effettivamente i puntatori e bloccare la memoria ovunque. Utilizzando i riferimenti gestiti si ottengono questi vantaggi senza pagare il costo di rovinare le prestazioni della raccolta dei rifiuti.

Abbiamo preso in considerazione questa funzione e ne abbiamo effettivamente implementato abbastanza da mostrarlo ad altri team interni per ottenere il loro feedback. Tuttavia, in questo momento, sulla base delle nostre ricerche , riteniamo che la funzione non abbia un appeal sufficientemente ampio o casi di utilizzo convincenti da trasformarla in una vera funzionalità linguistica supportata . Abbiamo altre priorità più alte e una quantità limitata di tempo e sforzi disponibili, quindi non faremo presto questa funzione.

Inoltre, farlo correttamente richiederebbe alcune modifiche al CLR. Al momento il CLR considera i metodi di restituzione come legali ma non verificabili perché non abbiamo un rilevatore che rilevi questa situazione:

ref int M1(ref int x)
{
    return ref x;
}

ref int M2()
{
    int y = 123;
    return ref M1(ref y); // Trouble!
}

int M3()
{
    ref int z = ref M2();
    return z;
}

M3 restituisce il contenuto della variabile locale M2, ma la durata di tale variabile è terminata! È possibile scrivere un rivelatore che determina gli usi dei ref-return che chiaramente non violano la sicurezza dello stack. Quello che faremmo è scrivere un tale rivelatore, e se il rivelatore non fosse in grado di provare la sicurezza dello stack, allora non permetteremmo l'uso dei ritorni di riferimento in quella parte del programma. Non è un'enorme quantità di lavoro di sviluppo per farlo, ma è un grande onere per i team di test per assicurarsi che abbiamo davvero tutti i casi. È solo un'altra cosa che aumenta il costo della funzionalità al punto in cui in questo momento i vantaggi non superano i costi.

Se riesci a descrivere per me perché vuoi questa funzione, lo apprezzerei molto . Più informazioni abbiamo da parte dei clienti reali sul motivo per cui lo desiderano, più è probabile che un giorno entrino nel prodotto. È una funzionalità carina e mi piacerebbe essere in grado di portarlo ai clienti in qualche modo se c'è abbastanza interesse.

(Vedi anche le domande correlate È possibile restituire un riferimento a una variabile in C #? E posso usare un riferimento all'interno di una funzione C # come C ++? )


4
@EricLippert: non ho un esempio convincente è solo qualcosa che mi chiedevo. Risposta eccellente e avvincente
Tom Sarduy

1
@Eric: Nel tuo esempio, non sarebbe più adatto a rimanere in y vita dopo il ritorno M2? Mi aspetterei che questa funzione funzioni come un lambda che cattura i locali. O è il comportamento che hai proposto perché è come CLR gestisce quello scenario?
Fede,

3
@Eric: IMHO la possibilità di avere proprietà che restituiscono riferimenti a tipi di valore è una grande omissione nei linguaggi .net. Se Arr è un array di un tipo di valore (ad esempio Point), si può dire ad esempio Arr (3) .X = 9 e sapere che non si è modificato il valore di Arr (9) .X o SomeOtherArray (2). X; se Arr fosse invece un array di qualche tipo di riferimento, non esisterebbero tali garanzie. Il fatto che l'operatore di indicizzazione di un array restituisca un riferimento è estremamente utile; Ritengo estremamente spiacevole che nessun altro tipo di raccolta sia in grado di fornire tale funzionalità.
supercat

4
Eric, qual è il modo migliore per fornire il feedback sugli scenari (come suggerisci nell'ultimo paragrafo) per massimizzare le possibilità che venga visto? MS Connect? UserVoice (con il suo limite arbitrario di 10 post / voti)? Qualcos'altro?
Roman Starkov,

1
@ThunderGr: Questa è la filosofia C # per "non sicuro" - se scrivi codice che è probabilmente non sicuro per la memoria, C # insiste sul fatto che lo contrassegni come "non sicuro", in modo da assumerti la responsabilità della sicurezza della memoria. C # ha già la versione non sicura della funzionalità, a condizione che la variabile in questione sia di tipo non gestito. La domanda è se il team C # debba creare una versione non sicura che gestisca i tipi gestiti. Se così facendo lo sviluppatore può scrivere facilmente bug terribili, non accadrà. C # non è C ++, un linguaggio che semplifica la scrittura di bug terribili. C # è sicuro dal design.
Eric Lippert,

21

Stai parlando di metodi che restituiscono un riferimento a un tipo di valore. L'unico esempio incorporato in C # che conosco è l'accessorio di array di un tipo di valore:

public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

e ora crea un array di quella struttura:

var points = new Point[10];
points[0].X = 1;
points[0].Y = 2;

In questo caso points[0], l' indicizzatore di array sta restituendo un riferimento a struct. È impossibile scrivere il proprio indicizzatore (ad esempio per una raccolta personalizzata), che ha lo stesso comportamento "restituisci un riferimento".

Non ho progettato il linguaggio C # quindi non conosco tutti i ragionamenti alla base del non supportarlo, ma penso che la risposta breve potrebbe essere: possiamo andare d'accordo senza di essa.


8
Tom sta chiedendo dei metodi che restituiscono un riferimento a una variabile . La variabile non deve necessariamente essere di tipo valore, anche se ovviamente è quello che le persone desiderano quando desiderano metodi di restituzione. Altrimenti, ottima analisi; Lei ha ragione che l'unico posto nel linguaggio C # in cui un'espressione complessa produce un ref a una variabile che l'utente può quindi manipolare è l'indicizzatore array. (E ovviamente l'operatore di accesso membro "." Tra un ricevitore e un campo, ma questo è ovviamente un accesso a una variabile.)
Eric Lippert

1

Puoi sempre fare qualcosa del tipo:

public delegate void MyByRefConsumer<T>(ref T val);

public void DoSomethingWithValueType(MyByRefConsumer<int> c)
{
        int x = 2;
        c(ref x);
        //Handle potentially changed x...
}

1

C # 7.0 ha il supporto per la restituzione di riferimenti. Vedi la mia risposta qui .

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.