Was ist die Verwendung von "ref" für Variablen vom Referenztyp in C #?


175

Ich verstehe , dass , wenn ich einen Wert vom Typ Pass ( int, struct, etc.) als Parameter (ohne das refSchlüsselwort), wird eine Kopie dieser Variablen auf die Methode übergeben wird, aber wenn ich die Verwendung refSchlüsselwort ein Verweis auf diese Variable übergeben wird , kein neuer.

Bei Referenztypen wie Klassen wird jedoch auch ohne das refSchlüsselwort eine Referenz an die Methode übergeben, keine Kopie. Wozu dient das refSchlüsselwort bei Referenztypen?


Nehmen Sie zum Beispiel:

var x = new Foo();

Was ist der Unterschied zwischen den folgenden?

void Bar(Foo y) {
    y.Name = "2";
}

und

void Bar(ref Foo y) {
    y.Name = "2";
}

Antworten:


153

Sie können ändern, welche fooPunkte verwendet werden sollen y:

Foo foo = new Foo("1");

void Bar(ref Foo y)
{
    y = new Foo("2");
}

Bar(ref foo);
// foo.Name == "2"

17
Sie erhalten also im Grunde einen Verweis auf den ursprünglichen Verweis
lhahne

2
Sie können ändern, worauf sich die ursprüngliche Referenz bezieht, also ja.
user7116

1
Chris, deine Erklärung ist großartig. Vielen Dank, dass Sie mir geholfen haben, dieses Konzept zu verstehen.
Andreas Grech

4
Die Verwendung von 'ref' für ein Objekt ist also wie die Verwendung von Doppelzeigern in C ++?
Tom Hazel

1
@TomHazel: -ish , vorausgesetzt, Sie verwenden "doppelte" Zeiger in C ++, um zu ändern, auf was ein Zeiger zeigt.
user7116

29

Es gibt Fälle, in denen Sie die tatsächliche Referenz und nicht das Objekt ändern möchten, auf das verwiesen wird:

void Swap<T>(ref T x, ref T y) {
    T t = x;
    x = y;
    y = t;
}

var test = new[] { "0", "1" };
Swap(ref test[0], ref test[1]);

21

Jon Skeet hat einen großartigen Artikel über die Parameterübergabe in C # geschrieben. Es beschreibt klar das genaue Verhalten und die Verwendung der Übergabe von Parametern nach Wert, Referenz ( ref) und Ausgabe ( out).

Hier ist ein wichtiges Zitat von dieser Seite in Bezug auf refParameter:

Referenzparameter übergeben nicht die Werte der Variablen, die beim Aufruf des Funktionselements verwendet werden - sie verwenden die Variablen selbst. Anstatt einen neuen Speicherort für die Variable in der Funktionselementdeklaration zu erstellen, wird derselbe Speicherort verwendet, sodass der Wert der Variablen im Funktionselement und der Wert des Referenzparameters immer gleich sind. Referenzparameter benötigen den Ref-Modifikator sowohl als Teil der Deklaration als auch des Aufrufs - das heißt, es ist immer klar, wenn Sie etwas als Referenz übergeben.


11
Ich mag die Analogie, Ihre Hundeleine an einen Freund weiterzugeben, um einen Referenz-Nachwert zu übergeben ... sie bricht jedoch schnell zusammen, weil ich denke, Sie würden es wahrscheinlich bemerken, wenn Ihr Freund Ihren Shitzu an einen Dobermann tauscht, bevor er Sie zurückgibt die Leine ;-)
Corlettk

16

Sehr schön hier erklärt: http://msdn.microsoft.com/en-us/library/s6938f28.aspx

Auszug aus dem Artikel:

Eine Variable eines Referenztyps enthält ihre Daten nicht direkt. es enthält einen Verweis auf seine Daten. Wenn Sie einen Parameter vom Typ Referenz als Wert übergeben, können Sie die Daten ändern, auf die die Referenz verweist, z. B. den Wert eines Klassenmitglieds. Sie können den Wert der Referenz selbst jedoch nicht ändern. Das heißt, Sie können nicht dieselbe Referenz verwenden, um Speicher für eine neue Klasse zuzuweisen und ihn außerhalb des Blocks beizubehalten. Übergeben Sie dazu den Parameter mit dem Schlüsselwort ref oder out.


4
Die Erklärung ist in der Tat sehr schön. Von Nur-Link-Antworten wird bei SO jedoch abgeraten. Ich habe eine Zusammenfassung aus dem Artikel hinzugefügt, um den Lesern hier eine Annehmlichkeit zu bieten.
Marcel

10

Wenn Sie einen Referenztyp mit dem Schlüsselwort ref übergeben, übergeben Sie die Referenz als Referenz, und die von Ihnen aufgerufene Methode kann dem Parameter einen neuen Wert zuweisen. Diese Änderung wird auf den aufrufenden Bereich übertragen. Ohne ref wird die Referenz als Wert übergeben, und dies geschieht nicht.

C # hat auch das Schlüsselwort 'out', das ref sehr ähnlich ist, außer dass mit 'ref' Argumente vor dem Aufruf der Methode initialisiert werden müssen und mit 'out' Sie einen Wert in der empfangenden Methode zuweisen müssen.


5

Hier können Sie die übergebene Referenz ändern, z

void Bar()
{
    var y = new Foo();
    Baz(ref y);
}

void Baz(ref Foo y)
{
    y.Name = "2";

    // Overwrite the reference
    y = new Foo();
}

Sie können auch verwenden , wenn Sie sich nicht für die übergebene Referenz interessieren:

void Bar()
{
    var y = new Foo();
    Baz(out y);
}

void Baz(out Foo y)
{
    // Return a new reference
    y = new Foo();
}

4

Noch ein Haufen Code

class O
{
    public int prop = 0;
}

class Program
{
    static void Main(string[] args)
    {
        O o1 = new O();
        o1.prop = 1;

        O o2 = new O();
        o2.prop = 2;

        o1modifier(o1);
        o2modifier(ref o2);

        Console.WriteLine("1 : " + o1.prop.ToString());
        Console.WriteLine("2 : " + o2.prop.ToString());
        Console.ReadLine();
    }

    static void o1modifier(O o)
    {
        o = new O();
        o.prop = 3;
    }

    static void o2modifier(ref O o)
    {
        o = new O();
        o.prop = 4;
    }
}

3

Zusätzlich zu den vorhandenen Antworten:

Wie Sie nach dem Unterschied der beiden Methoden gefragt haben: Es gibt keine Co (ntra) -Varianz bei der Verwendung von refoder out:

class Foo { }
class FooBar : Foo { }

static void Bar(Foo foo) { }
static void Bar(ref Foo foo) { foo = new Foo(); }

void Main()
{
    Foo foo = null;
    Bar(foo);           // OK
    Bar(ref foo);       // OK

    FooBar fooBar = null;
    Bar(fooBar);        // OK (covariance)
    Bar(ref fooBar);    // compile time error
}

1

Ein Parameter in einer Methode scheint immer eine Kopie zu übergeben, die Frage ist eine Kopie von was. Eine Kopie wird von einem Kopierkonstruktor für ein Objekt erstellt. Da alle Variablen in C # Objekt sind, glaube ich, dass dies für alle der Fall ist. Variablen (Objekte) sind wie Menschen, die an bestimmten Adressen leben. Wir ändern entweder die Personen, die an diesen Adressen leben, oder wir können weitere Verweise auf die Personen erstellen, die an diesen Adressen im Telefonbuch leben (flache Kopien erstellen). Es kann also mehr als eine Kennung auf dieselbe Adresse verweisen. Referenztypen benötigen mehr Speicherplatz. Im Gegensatz zu Werttypen, die direkt durch einen Pfeil mit ihrer Kennung im Stapel verbunden sind, haben sie einen Wert für eine andere Adresse im Heap (einen größeren Speicherplatz zum Verweilen). Dieser Platz muss vom Haufen genommen werden.

Werttyp: Einrücker (enthält Wert = Adresse des Stapelwerts) ----> Wert des Werttyps

Referenztyp: Bezeichner (enthält Wert = Adresse des Stapelwerts) ----> (enthält Wert = Adresse des Heap-Werts) ----> Heap-Wert (enthält meistens Adressen zu anderen Werten), stellen Sie sich vor, dass mehr Pfeile in verschiedenen Bereichen stecken Richtungen zu Array [0], Array [1], Array [2]

Die einzige Möglichkeit, einen Wert zu ändern, besteht darin, den Pfeilen zu folgen. Wenn ein Pfeil verloren geht oder geändert wird, ist der Wert nicht erreichbar.


-1

Referenzvariablen tragen die Adresse von einem Ort zum anderen, sodass jede Aktualisierung an jedem Ort alle Orte widerspiegelt, DANN was die Verwendung von REF ist. Die Referenzvariable (405) ist so lange gültig, bis der in der Methode übergebenen Referenzvariablen kein neuer Speicher zugewiesen wird.

Sobald ein neuer Speicher zugewiesen wurde (410), wird die Wertänderung für dieses Objekt (408) nicht überall wiedergegeben. Dafür kommt ref. Ref ist eine Referenzreferenz. Wenn also ein neuer Speicher zugewiesen wird, wird er bekannt, da er auf diesen Speicherort verweist. Daher kann der Wert von everyOne gemeinsam genutzt werden. Sie können das Bild für mehr Klarheit sehen.

Siehe Referenzvariable

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.