Wann ref verwendet werden soll und wann es in C # nicht erforderlich ist

104

Ich habe ein Objekt, das sich im Speicherstatus des Programms befindet, und einige andere Worker-Funktionen, an die ich das Objekt übergebe, um den Status zu ändern. Ich habe es per Ref an die Arbeiterfunktionen weitergegeben. Ich bin jedoch auf die folgende Funktion gestoßen.

byte[] received_s = new byte[2048];
IPEndPoint tmpIpEndPoint = new IPEndPoint(IPAddress.Any, UdpPort_msg);
EndPoint remoteEP = (tmpIpEndPoint);

int sz = soUdp_msg.ReceiveFrom(received_s, ref remoteEP); 

Es verwirrt mich, weil beide received_sund remoteEPSachen von der Funktion zurückgeben. Warum braucht remoteEPman ein refund received_snicht?

Ich bin auch ein AC-Programmierer, daher habe ich ein Problem damit, Zeiger aus meinem Kopf zu bekommen.

Bearbeiten: Es sieht so aus, als wären Objekte in C # Zeiger auf das Objekt unter der Haube. Wenn Sie also ein Objekt an eine Funktion übergeben, können Sie den Objektinhalt über den Zeiger ändern. Das einzige, was an die Funktion übergeben wird, ist der Zeiger auf das Objekt, damit das Objekt selbst nicht kopiert wird. Sie verwenden ref oder out, wenn Sie in der Funktion, die einem Doppelzeiger ähnelt, ausschalten oder ein neues Objekt erstellen möchten.

Rex Logan
quelle

Antworten:

216

Kurze Antwort: Lesen Sie meinen Artikel über das Bestehen von Argumenten .

Lange Antwort: Wenn ein Referenztypparameter als Wert übergeben wird, wird nur die Referenz übergeben, keine Kopie des Objekts. Dies ist wie das Übergeben eines Zeigers (nach Wert) in C oder C ++. Änderungen an den Wert des Parameters selbst wird nicht durch den Anrufer zu sehen ist, sondern ändert sich in dem Objekt , das die Referenzpunkte werden zu sehen.

Wenn ein Parameter (jeglicher Art) geleitet wird , durch Bezugnahme sind das bedeutet , dass alle Änderungen der Parameter durch den Anrufer gesehen - Änderungen der Parameter sind Änderungen der Variablen.

Der Artikel erklärt das alles natürlich ausführlicher :)

Nützliche Antwort: Sie müssen ref / out fast nie verwenden . Dies ist im Grunde ein Weg, um einen anderen Rückgabewert zu erhalten, und sollte normalerweise vermieden werden, gerade weil dies bedeutet, dass die Methode wahrscheinlich versucht, zu viel zu tun. Dies ist nicht immer der Fall ( TryParseusw. sind die kanonischen Beispiele für eine vernünftige Verwendung von out), aber die Verwendung von ref / out sollte eine relative Seltenheit sein.

Jon Skeet
quelle
38
Ich denke, Sie haben Ihre kurze und lange Antwort durcheinander gebracht. Das ist ein großer Artikel!
Outlaw Programmer
23
@Outlaw: Ja, aber die kurze Antwort selbst, die Anweisung zum Lesen des Artikels, ist nur 6 Wörter lang :)
Jon Skeet
27
Eine Referenz! :)
Gonzobrains
5
@Liam Wenn Sie ref wie Sie verwenden, wird dies möglicherweise expliziter für Sie angezeigt, aber es kann für andere Programmierer (diejenigen, die wissen, was dieses Schlüsselwort sowieso tut) verwirrend sein, da Sie potenziellen Anrufern im Wesentlichen sagen: "Ich kann die Variable ändern, die Sie verwenden." Wird in der aufrufenden Methode verwendet, dh weist es einem anderen Objekt neu zu (oder wenn möglich sogar null). Halten Sie sich also nicht daran fest, oder stellen Sie sicher, dass Sie es validieren, wenn ich damit fertig bin. " Das ist ziemlich stark und völlig anders als "Dieses Objekt kann geändert werden", was immer dann der Fall ist, wenn Sie eine Objektreferenz als Parameter übergeben.
mbargiel
1
@ M.Mimpen: C # 7 wird (hoffentlich) erlaubenif (int.TryParse(text, out int value)) { ... use value here ... }
Jon Skeet
26

Stellen Sie sich einen Nicht-Ref-Parameter als Zeiger und einen Ref-Parameter als Doppelzeiger vor. Das hat mir am meisten geholfen.

Sie sollten Werte fast nie mit ref übergeben. Ich vermute, dass das .Net-Team es ohne Interop-Bedenken niemals in die ursprüngliche Spezifikation aufgenommen hätte. Die OO-Methode, um mit den meisten Problemen umzugehen, die ref-Parameter lösen, ist:

Für mehrere Rückgabewerte

  • Erstellen Sie Strukturen, die die mehreren Rückgabewerte darstellen

Für Grundelemente, die sich in einer Methode infolge des Methodenaufrufs ändern (Methode hat Nebenwirkungen auf Grundelemente)

  • Implementieren Sie die Methode in einem Objekt als Instanzmethode und bearbeiten Sie den Status des Objekts (nicht die Parameter) als Teil des Methodenaufrufs
  • Verwenden Sie die Lösung mit mehreren Rückgabewerten und führen Sie die Rückgabewerte in Ihrem Status zusammen
  • Erstellen Sie ein Objekt, das einen Status enthält, der von einer Methode bearbeitet werden kann, und übergeben Sie dieses Objekt als Parameter und nicht die Grundelemente selbst.
Michael Meadows
quelle
8
Guter Gott. Ich müsste das 20x lesen, um es zu verstehen. Klingt für mich nach einer Menge zusätzlicher Arbeit, nur um etwas Einfaches zu tun.
PositiveGuy
Das .NET Framework folgt nicht Ihrer Regel Nr. 1. ('Für mehrere Rückgabewerte Strukturen erstellen'). Nehmen wir zum Beispiel IPAddress.TryParse(string, out IPAddress).
Swen Kooij
@SwenKooij Du hast recht. Ich gehe davon aus, dass an den meisten Stellen, an denen sie Parameter verwenden, entweder (a) eine Win32-API verpackt wird oder (b) es noch früh war und die C ++ - Programmierer Entscheidungen getroffen haben.
Michael Meadows
@SwenKooij Ich weiß, dass diese Antwort viele Jahre zu spät ist, aber es tut. Wir sind an TryParse gewöhnt, aber das bedeutet nicht, dass es gut ist. Es wäre besser gewesen, als if (int.TryParse("123", out var theInt) { /* use theInt */ }wir es getan hätten. var candidate = int.TrialParse("123"); if (candidate.Parsed) { /* do something with candidate.Value */ }Es ist mehr Code, aber es stimmt viel besser mit dem Design der C # -Sprache überein.
Michael Meadows
9

Sie könnten wahrscheinlich eine ganze C # -App schreiben und niemals Objekte / Strukturen per Referenz übergeben.

Ich hatte einen Professor, der mir folgendes sagte:

Der einzige Ort, an dem Sie Refs verwenden würden, ist der Ort, an dem Sie entweder:

  1. Sie möchten ein großes Objekt übergeben (dh die Objekte / Struktur enthält Objekte / Strukturen auf mehreren Ebenen), und das Kopieren wäre teuer und
  2. Sie rufen ein Framework, eine Windows-API oder eine andere API auf, die dies erfordert.

Tu es nicht nur, weil du kannst. Sie können durch einige böse Fehler in den Arsch gebissen werden, wenn Sie anfangen, die Werte in einem Parameter zu ändern und nicht aufpassen.

Ich stimme seinem Rat zu und in meinen mehr als fünf Jahren seit der Schule hatte ich nie einen Bedarf dafür, außer das Framework oder die Windows-API aufzurufen.

Chris
quelle
3
Wenn Sie "Swap" implementieren möchten, kann es hilfreich sein, ref zu übergeben.
Brian
@ Chris wird es ein Problem machen, wenn ich das Schlüsselwort ref für kleine Objekte verwende?
ManirajSS
@TechnikEmpire "Änderungen am Objekt im Rahmen der aufgerufenen Funktion werden jedoch nicht an den Aufrufer zurückgegeben." Das ist falsch. Wenn ich eine Person weitergebe SetName(person, "John Doe"), ändert sich die Namenseigenschaft und diese Änderung wird für den Anrufer übernommen.
M. Mimpen
@ M.Mimpen Kommentar gelöscht. Ich hatte zu diesem Zeitpunkt kaum C # aufgenommen und sprach deutlich mein * $$ aus. Vielen Dank, dass Sie mich darauf aufmerksam gemacht haben.
@ Chris - Ich bin mir ziemlich sicher, dass das Übergeben eines "großen" Objekts nicht teuer ist. Wenn Sie es nur als Wert übergeben, übergeben Sie immer noch nur einen einzelnen Zeiger, oder?
Ian
3

Da receive_s ein Array ist, übergeben Sie einen Zeiger auf dieses Array. Die Funktion manipuliert die vorhandenen Daten, ohne die zugrunde liegende Position oder den zugrunde liegenden Zeiger zu ändern. Das Schlüsselwort ref gibt an, dass Sie den tatsächlichen Zeiger an die Position übergeben und diesen Zeiger in der Außenfunktion aktualisieren, sodass sich der Wert in der Außenfunktion ändert.

Beispielsweise ist das Bytearray ein Zeiger auf denselben Speicher, bevor und nachdem der Speicher gerade aktualisiert wurde.

Die Endpunktreferenz aktualisiert tatsächlich den Zeiger auf den Endpunkt in der externen Funktion auf eine neue Instanz, die innerhalb der Funktion generiert wird.

Chris Hynes
quelle
3

Stellen Sie sich einen Verweis so vor, als würden Sie einen Zeiger als Referenz übergeben. Wenn Sie keine Referenz verwenden, übergeben Sie einen Zeiger als Wert.

Besser noch, ignorieren Sie, was ich gerade gesagt habe (es ist wahrscheinlich irreführend, insbesondere bei Werttypen) und lesen Sie diese MSDN-Seite .

Brian
quelle
Eigentlich nicht wahr. Zumindest der zweite Teil. Jeder Referenztyp wird immer als Referenz übergeben, unabhängig davon, ob Sie ref verwenden oder nicht.
Erik Funkenbusch
Nach weiteren Überlegungen kam das eigentlich nicht richtig heraus. Die Referenz wird tatsächlich als Wert ohne den Ref-Typ übergeben. Das heißt, durch Ändern der Werte, auf die die Referenz zeigt, werden die Originaldaten geändert, durch Ändern der Referenz selbst wird jedoch die ursprüngliche Referenz nicht geändert.
Erik Funkenbusch
2
Ein Referenztyp ohne Referenz wird nicht als Referenz übergeben. Ein Verweis auf den Referenztyp wird als Wert übergeben. Aber wenn Sie sich eine Referenz als Zeiger auf etwas vorstellen möchten, auf das verwiesen wird, ist das, was ich gesagt habe, sinnvoll (aber so zu denken kann irreführend sein). Daher meine Warnung.
Brian
Link ist tot - versuchen Sie es stattdessen - docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
GIVE-ME-CHICKEN
0

Mein Verständnis ist, dass alle von der Object-Klasse abgeleiteten Objekte als Zeiger übergeben werden, während gewöhnliche Typen (int, struct) nicht als Zeiger übergeben werden und ref erfordern. Ich bin mir bei der Zeichenfolge nicht sicher (wird sie letztendlich von der Objektklasse abgeleitet?)

Lucas
quelle
Dies könnte eine Klarstellung gebrauchen. Was ist der Unterschied zwischen Wert- und Referenztypen? Ans, warum ist das für die Verwendung des Schlüsselworts ref für einen Parameter relevant?
oɔɯǝɹ
In .net wird alles außer Zeigern, Typparametern und Schnittstellen von Object abgeleitet. Es ist wichtig zu verstehen, dass "normale" Typen (korrekt als "Werttypen" bezeichnet) auch vom Objekt erben. Von da an haben Sie Recht: Werttypen (standardmäßig) werden als Wert übergeben, während Referenztypen als Referenz übergeben werden. Wenn Sie einen Werttyp ändern möchten, anstatt einen neuen zurückzugeben (behandeln Sie ihn wie einen Referenztyp innerhalb einer Methode), müssen Sie das Schlüsselwort ref verwenden. Aber das ist ein schlechter Stil und du solltest das einfach nicht tun, es sei denn du bist absolut sicher, dass du musst :)
Buddybubble
1
Um Ihre Frage zu beantworten: Zeichenfolge wird vom Objekt abgeleitet. Es ist ein Referenztyp, der sich wie ein Werttyp verhält (aus Leistungs- und logischen Gründen)
Buddybubble
0

Obwohl ich der allgemeinen Antwort von Jon Skeet und einigen anderen Antworten zustimme, gibt es einen Anwendungsfall für die Verwendung ref, und zwar zur Verschärfung der Leistungsoptimierungen. Bei der Leistungsprofilerstellung wurde beobachtet, dass das Festlegen des Rückgabewerts einer Methode geringfügige Auswirkungen auf die Leistung hat, wohingegen die Verwendung refals Argument, bei dem der Rückgabewert in diesen Parameter eingefügt wird, dazu führt, dass dieser geringfügige Engpass beseitigt wird.

Dies ist wirklich nur dann nützlich, wenn Optimierungsbemühungen auf ein extremes Niveau gebracht werden, wodurch die Lesbarkeit und möglicherweise die Testbarkeit und Wartbarkeit beeinträchtigt werden, um Millisekunden oder möglicherweise Split-Millisekunden zu sparen.

Jon Davis
quelle
-1

Grundnullregel zuerst: Primitive werden im Kontext der beteiligten TYPEN als Wert (Stapel) und Nicht-Primitiv als Referenz (Heap) übergeben.

Die beteiligten Parameter werden standardmäßig als Wert übergeben. Guter Beitrag, der die Dinge im Detail erklärt. http://yoda.arachsys.com/csharp/parameters.html

Student myStudent = new Student {Name="A",RollNo=1};

ChangeName(myStudent);

static void ChangeName(Student s1)
{
  s1.Name = "Z"; // myStudent.Name will also change from A to Z
                // {AS s1 and myStudent both refers to same Heap(Memory)
                //Student being the non-Primitive type
}

ChangeNameVersion2(ref myStudent);
static void ChangeNameVersion2(ref Student s1)
{
  s1.Name = "Z"; // Not any difference {same as **ChangeName**}
}

static void ChangeNameVersion3(ref Student s1)
{
    s1 = new Student{Name="Champ"};

    // reference(myStudent) will also point toward this new Object having new memory
    // previous mystudent memory will be released as it is not pointed by any object
}

Wir können (mit Warnung) sagen, dass nicht-primitive Typen nichts als Zeiger sind. Und wenn wir sie als Referenz übergeben, können wir sagen, dass wir Doppelzeiger übergeben

Surender Singh Malik
quelle
Alle Parameter werden standardmäßig in C # als Wert übergeben. Jeder Parameter kann als Referenz übergeben werden. Bei Referenztypen ist der übergebene Wert (entweder als Referenz oder als Wert) selbst eine Referenz. Das ist völlig unabhängig davon, wie es weitergegeben wird.
Servy
Stimmen Sie @Servy zu! "Wenn wir die verwendeten Wörter" Referenz "oder" Wert "hören, sollten wir uns sehr klar darüber sein, ob wir gemeint haben, dass ein Parameter eine Referenz oder ein Wertparameter ist, oder ob wir meinen, dass der betreffende Typ eine Referenz oder ein Werttyp 'Little" ist Verwirrung da draußen auf meiner Seite, WENN ich zuerst die Ground-Zero-Regel sagte , werden Primitive als Wert (Stapel) und Nicht-Primitiv als Referenz (Heap) übergeben, was ich mit den beteiligten TYPEN meinte, nicht mit dem Parameter! Wenn wir über Parameter sprechen, sind Sie korrekt Alle Parameter werden standardmäßig in C # als Wert übergeben.
Surender Singh Malik
Zu sagen, dass ein Parameter ein Referenz- oder Wertparameter ist, ist kein Standardbegriff und wirklich unklar, was Sie meinen. Der Typ des Parameters kann entweder ein Referenztyp oder ein Werttyp sein. Jeder Parameter kann als Wert oder als Referenz übergeben werden. Dies sind orthogonale Konzepte für einen bestimmten Parameter. Ihr Beitrag beschreibt dies falsch.
Servy
1
Sie übergeben Parameter als Referenz oder als Wert. Die Begriffe "nach Referenz" und "nach Wert" werden nicht verwendet, um zu beschreiben, ob ein Typ ein Werttyp oder ein Referenztyp ist.
Servy
1
Nein, es geht nicht um Wahrnehmung. Wenn Sie den falschen Begriff verwenden, um sich auf etwas zu beziehen, wird diese Aussage falsch . Für korrekte Aussagen ist es wichtig, die richtige Terminologie zu verwenden, um auf Konzepte zu verweisen.
Servy