Warum unterstützt C # die Rückgabe von Referenzen nicht?


141

Ich habe gelesen, dass .NET die Rückgabe von Referenzen unterstützt, C # jedoch nicht. Gibt es einen besonderen Grund? Warum ich so etwas nicht machen kann:

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

8
ähm ... Zitat bitte?
RPM1984

5
C # hat Wert- und Referenztypen und beide können zurückgegeben werden, was Sie meinen.
Wiederholung

Ich spreche von so etwas wiereturn ref x
Tom Sarduy

2
Nach 4 Jahren können Sie genau das mit C# 7:)
Arghya C

Antworten:


188

Diese Frage war das Thema meines Blogs am 23. Juni 2011 . Danke für die tolle Frage!

Das C # -Team erwägt dies für C # 7. Weitere Informationen finden Sie unter https://github.com/dotnet/roslyn/issues/5233 .

UPDATE: Die Funktion hat es in C # 7 geschafft!


Du hast Recht; .NET unterstützt Methoden, die verwaltete Verweise auf Variablen zurückgeben. .NET unterstützt auch lokale Variablen , die verwaltete Verweise auf andere Variablen enthalten. (Beachten Sie jedoch, dass .NET keine Felder oder Arrays unterstützt , die verwaltete Verweise auf andere Variablen enthalten, da dies die Garbage Collection-Story übermäßig kompliziert. Auch die Typen "verwalteter Verweis auf Variablen" sind nicht in Objekte konvertiert werden und werden daher möglicherweise nicht als verwendet Typargumente für generische Typen oder Methoden.)

Der Kommentator "RPM1984" bat aus irgendeinem Grund um ein Zitat für diese Tatsache. RPM1984 Ich empfehle Ihnen, die CLI-Spezifikation Partition I Abschnitt 8.2.1.1, "Verwaltete Zeiger und verwandte Typen" zu lesen, um Informationen zu dieser Funktion von .NET zu erhalten.

Es ist durchaus möglich, eine Version von C # zu erstellen, die beide Funktionen unterstützt. Sie könnten dann Dinge wie tun

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

und dann nenne es mit

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

Ich weiß empirisch, dass es möglich ist, eine Version von C # zu erstellen, die diese Funktionen unterstützt weil ich dies getan habe . Fortgeschrittene Programmierer, insbesondere Leute, die nicht verwalteten C ++ - Code portieren, fragen uns oft nach mehr C ++ - ähnlichen Möglichkeiten, Dinge mit Referenzen zu tun, ohne den großen Hammer herausholen zu müssen, Zeiger zu verwenden und Speicher überall festzunageln. Durch die Verwendung verwalteter Referenzen erhalten Sie diese Vorteile, ohne die Kosten für die Leistungssteigerung Ihrer Speicherbereinigung zu bezahlen.

Wir haben diese Funktion in Betracht gezogen und tatsächlich genug implementiert, um sie anderen internen Teams zu zeigen, um deren Feedback zu erhalten. Aufgrund unserer Untersuchungen glauben wir jedoch, dass das Feature derzeit nicht breit genug ansprechend oder überzeugend ist, um es zu einem wirklich unterstützten Sprachfeature zu machen . Wir haben andere höhere Prioritäten und einen begrenzten Zeit- und Arbeitsaufwand zur Verfügung, daher werden wir diese Funktion in Kürze nicht mehr ausführen.

Um es richtig zu machen, müssten einige Änderungen an der CLR vorgenommen werden. Derzeit behandelt die CLR Rückgabemethoden als legal, aber nicht überprüfbar, da wir keinen Detektor haben, der diese Situation erkennt:

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 gibt den Inhalt der lokalen Variablen von M2 zurück, aber die Lebensdauer dieser Variablen ist abgelaufen! Es ist möglich, einen Detektor zu schreiben, der die Verwendung von Ref-Returns bestimmt, die die Stapelsicherheit eindeutig nicht verletzen. Wir würden einen solchen Detektor schreiben, und wenn der Detektor die Stapelsicherheit nicht nachweisen könnte, würden wir die Verwendung von Ref-Rückgaben in diesem Teil des Programms nicht zulassen. Es ist keine große Menge an Entwicklungsarbeit, aber es ist eine große Belastung für die Testteams, sicherzustellen, dass wir wirklich alle Fälle haben. Es ist nur eine andere Sache, die die Kosten der Funktion so weit erhöht, dass die Vorteile derzeit die Kosten nicht überwiegen.

Wenn Sie mir beschreiben können, warum Sie diese Funktion wünschen, würde ich das sehr begrüßen . Je mehr Informationen wir von echten Kunden darüber haben, warum sie es wollen, desto wahrscheinlicher wird es eines Tages in das Produkt gelangen. Es ist ein süßes kleines Feature und ich würde es gerne irgendwie zu Kunden bringen können, wenn es genügend Interesse gibt.

(Siehe auch verwandte Fragen. Ist es möglich, eine Referenz auf eine Variable in C # zurückzugeben? Und kann ich eine Referenz in einer C # -Funktion wie C ++ verwenden? )


4
@EricLippert: Ich habe kein überzeugendes Beispiel, ich habe mich nur gefragt. Ausgezeichnete und überzeugende Antwort
Tom Sarduy

1
@Eric: Wäre es in Ihrem Beispiel nicht besser geeignet, nach der Rückkehr am y Leben zu bleiben M2? Ich würde erwarten, dass diese Funktion wie Lambdas funktioniert, die Einheimische erfassen. Oder ist das von Ihnen vorgeschlagene Verhalten so, wie CLR mit diesem Szenario umgeht?
Fede

3
@Eric: IMHO ist die Möglichkeit, dass Eigenschaften Verweise auf Werttypen zurückgeben, eine wichtige Lücke in den .net-Sprachen. Wenn Arr ein Array eines Werttyps ist (z. B. Punkt), kann man zB Arr (3) .X = 9 sagen und wissen, dass man den Wert von Arr (9) .X oder sogar SomeOtherArray (2) nicht geändert hat. X; Wenn Arr stattdessen ein Array eines Referenztyps wäre, gäbe es keine solchen Garantien. Die Tatsache, dass der Indexierungsoperator eines Arrays eine Referenz zurückgibt, ist äußerst nützlich. Ich halte es für sehr bedauerlich, dass keine andere Art von Sammlung solche Funktionen bieten kann.
Supercat

4
Eric, wie kann ich am besten Feedback zu Szenarien geben (wie Sie im letzten Absatz vorschlagen), um die Chancen zu maximieren, die sich daraus ergeben? MS Connect? UserVoice (mit einem willkürlichen Limit von 10 Posts / Stimmen)? Etwas anderes?
Roman

1
@ThunderGr: Dies ist die C # -Philosophie für "unsicher". Wenn Sie Code schreiben, der möglicherweise speichersicher ist, besteht C # darauf, dass Sie ihn als "unsicher" markieren, damit Sie die Verantwortung für die Speichersicherheit übernehmen. C # verfügt bereits über die unsichere Version der Funktion, vorausgesetzt, die betreffende Variable ist vom nicht verwalteten Typ. Die Frage ist, ob das C # -Team eine unsichere Version erstellen soll, die verwaltete Typen verarbeitet. Wenn dies dem Entwickler das Schreiben schrecklicher Fehler erleichtert, wird dies nicht passieren. C # ist nicht C ++, eine Sprache, die es einfach macht, schreckliche Fehler zu schreiben. C # ist von Natur aus sicher.
Eric Lippert

21

Sie sprechen von Methoden, die einen Verweis auf einen Werttyp zurückgeben. Das einzige in C # integrierte Beispiel, das mir bekannt ist, ist der Array-Accessor eines Werttyps:

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

und jetzt erstellen Sie ein Array dieser Struktur:

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

In diesem Fall points[0]gibt der Array- Indexer einen Verweis auf struct zurück. Es ist unmöglich, einen eigenen Indexer zu schreiben (z. B. für eine benutzerdefinierte Sammlung), der dasselbe Verhalten wie "Referenz zurückgeben" aufweist.

Ich habe die C # -Sprache nicht entworfen, daher kenne ich nicht alle Gründe dafür, sie nicht zu unterstützen, aber ich denke, die kurze Antwort könnte lauten: Wir können ohne sie gut miteinander auskommen.


8
Tom fragt nach Methoden, die einen Verweis auf eine Variable zurückgeben . Die Variable muss nicht vom Werttyp sein, obwohl dies normalerweise normalerweise der Fall ist, wenn Benutzer Methoden zur Rückgabe von Refs wünschen. Ansonsten tolle Analyse; Sie haben Recht, dass der Array-Indexer die einzige Stelle in der C # -Sprache ist, an der ein komplexer Ausdruck einen Verweis auf eine Variable erzeugt, die der Benutzer dann bearbeiten kann. (Und natürlich der Mitgliedszugriffsoperator "." Zwischen einem Empfänger und einem Feld, aber das ist ziemlich offensichtlich ein Zugriff auf eine Variable.)
Eric Lippert

1

Sie könnten immer so etwas tun wie:

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

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

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.