Warum das Schlüsselwort 'ref' verwenden, wenn Sie ein Objekt übergeben?

290

Wenn ich ein Objekt an eine Methode übergebe, warum sollte ich das Schlüsselwort ref verwenden? Ist das nicht sowieso das Standardverhalten?

Beispielsweise:

class Program
{
    static void Main(string[] args)
    {
        TestRef t = new TestRef();
        t.Something = "Foo";

        DoSomething(t);
        Console.WriteLine(t.Something);
    }

    static public void DoSomething(TestRef t)
    {
        t.Something = "Bar";
    }
}


public class TestRef
{
    public string Something { get; set; }
}

Die Ausgabe ist "Bar", was bedeutet, dass das Objekt als Referenz übergeben wurde.

Ryan
quelle

Antworten:

298

Übergeben Sie a, refwenn Sie das Objekt ändern möchten:

TestRef t = new TestRef();
t.Something = "Foo";
DoSomething(ref t);

void DoSomething(ref TestRef t)
{
  t = new TestRef();
  t.Something = "Not just a changed t, but a completely different TestRef object";
}

Bezieht tsich nach dem Aufruf von DoSomething nicht auf das Original new TestRef, sondern auf ein völlig anderes Objekt.

Dies kann auch nützlich sein, wenn Sie den Wert eines unveränderlichen Objekts ändern möchten, z string. B. a . Sie können den Wert von a nach seiner stringErstellung nicht mehr ändern . Mit a refkönnen Sie jedoch eine Funktion erstellen, die die Zeichenfolge für eine andere mit einem anderen Wert ändert.

Bearbeiten: Wie andere Leute erwähnt haben. Es ist keine gute Idee, es zu verwenden, es refsei denn, es wird benötigt. Wenn Sie refder Methode die Freiheit geben, das Argument für etwas anderes zu ändern, müssen Aufrufer der Methode codiert werden, um sicherzustellen, dass sie diese Möglichkeit nutzen.

Wenn der Parametertyp ein Objekt ist, fungieren Objektvariablen immer als Verweise auf das Objekt. Dies bedeutet, dass Sie bei Verwendung des refSchlüsselworts einen Verweis auf einen Verweis haben. Auf diese Weise können Sie die im obigen Beispiel beschriebenen Schritte ausführen. Wenn der Parametertyp jedoch ein primitiver Wert ist (z. B. int), wird der Wert des übergebenen Arguments nach der Rückgabe der Methode geändert, wenn dieser Parameter innerhalb der Methode zugewiesen wird:

int x = 1;
Change(ref x);
Debug.Assert(x == 5);
WillNotChange(x);
Debug.Assert(x == 5); // Note: x doesn't become 10

void Change(ref int x)
{
  x = 5;
}

void WillNotChange(int x)
{
  x = 10;
}
Scott Langham
quelle
88

Sie müssen unterscheiden zwischen "Übergeben einer Referenz als Wert" und "Übergeben eines Parameters / Arguments als Referenz".

Ich habe einen ziemlich langen Artikel zu diesem Thema geschrieben , um zu vermeiden, dass ich jedes Mal, wenn dies in Newsgroups auftaucht, sorgfältig schreiben muss :)

Jon Skeet
quelle
1
Nun, ich bin auf das Problem beim Upgrade von VB6 auf .Net C # -Code gestoßen. Es gibt Funktions- / Methodensignaturen, die ref-, out- und Plain-Parameter verwenden. Wie können wir also den Unterschied zwischen einem einfachen Parameter und einem Schiedsrichter besser unterscheiden?
BonCodigo
2
@bonCodigo: Sie sind sich nicht sicher, was Sie unter "besser unterscheiden" verstehen - es ist Teil der Signatur, und Sie müssen es auch refan der Anrufstelle angeben ... wo sonst möchten Sie, dass es unterschieden wird? Die Semantik ist auch einigermaßen klar, muss aber sorgfältig ausgedrückt werden (anstatt "Objekte werden als Referenz übergeben", was die übliche Übervereinfachung ist).
Jon Skeet
Ich weiß nicht, warum Visual Studio immer noch nicht explizit zeigt, was übergeben wird
MonsterMMORPG
3
@MonsterMMORPG: Ich fürchte, ich weiß nicht, was du damit meinst.
Jon Skeet
56

Wenn Sie in .NET einen Parameter an eine Methode übergeben, wird eine Kopie erstellt. In Werttypen bedeutet, dass alle Änderungen, die Sie am Wert vornehmen, im Methodenbereich liegen und beim Beenden der Methode verloren gehen.

Wenn Sie einen Referenztyp übergeben, wird auch eine Kopie erstellt, es handelt sich jedoch um eine Kopie einer Referenz, dh Sie haben jetzt ZWEI Referenzen im Speicher auf dasselbe Objekt. Wenn Sie also die Referenz zum Ändern des Objekts verwenden, wird es geändert. Wenn Sie jedoch die Referenz selbst ändern - wir müssen uns daran erinnern, dass es sich um eine Kopie handelt -, gehen alle Änderungen auch beim Beenden der Methode verloren.

Wie bereits erwähnt, ist eine Zuordnung eine Änderung der Referenz und geht somit verloren:

public void Method1(object obj) {   
 obj = new Object(); 
}

public void Method2(object obj) {  
 obj = _privateObject; 
}

Die obigen Methoden ändern das ursprüngliche Objekt nicht.

Eine kleine Modifikation Ihres Beispiels

 using System;

    class Program
        {
            static void Main(string[] args)
            {
                TestRef t = new TestRef();
                t.Something = "Foo";

                DoSomething(t);
                Console.WriteLine(t.Something);

            }

            static public void DoSomething(TestRef t)
            {
                t = new TestRef();
                t.Something = "Bar";
            }
        }



    public class TestRef
    {
    private string s;
        public string Something 
        { 
            get {return s;} 
            set { s = value; }
        }
    }
Ricardo Amores
quelle
6
Diese Antwort gefällt mir besser als die akzeptierte Antwort. Es wird klarer erklärt, was passiert, wenn eine Referenztypvariable mit dem Schlüsselwort ref übergeben wird. Danke dir!
Stefan
17

Da TestRef eine Klasse ist (die Referenzobjekte sind), können Sie den Inhalt in t ändern, ohne ihn als Referenz zu übergeben. Wenn Sie jedoch t als Referenz übergeben, kann TestRef ändern, worauf sich das ursprüngliche t bezieht. dh lassen Sie es auf ein anderes Objekt zeigen.

Ferruccio
quelle
16

Mit können refSie schreiben:

static public void DoSomething(ref TestRef t)
{
    t = new TestRef();
}

Und t wird nach Abschluss der Methode geändert.

Rinat Abdullin
quelle
8

Stellen Sie sich Variablen (z. B. foo) von Referenztypen (z. B. List<T>) als Objektkennungen der Form "Objekt # 24601" vor. Angenommen, die Anweisung foo = new List<int> {1,5,7,9};bewirkt foo, dass "Objekt # 24601" (eine Liste mit vier Elementen) enthalten ist. Dann foo.Lengthfragt der Aufruf das Objekt Nr. 24601 nach seiner Länge und antwortet mit 4, also foo.Lengthgleich 4.

Wenn fooes ohne Verwendung an eine Methode übergeben wird ref, kann diese Methode Änderungen an Objekt # 24601 vornehmen. Infolge solcher Änderungen ist foo.Lengthmöglicherweise nicht mehr gleich 4. Die Methode selbst kann sich jedoch nicht ändern foo, da sie weiterhin "Objekt # 24601" enthält.

Durch die Übergabe fooals refParameter kann die aufgerufene Methode Änderungen nicht nur an Objekt Nr. 24601, sondern auch an sich fooselbst vornehmen . Die Methode erstellt möglicherweise ein neues Objekt # 8675309 und speichert einen Verweis darauf in foo. In diesem Fall foowürde nicht mehr "Objekt # 24601", sondern "Objekt # 8675309" gespeichert.

In der Praxis enthalten Variablen vom Referenztyp keine Zeichenfolgen der Form "Object # 8675309". Sie enthalten nicht einmal etwas, das sinnvoll in eine Zahl umgewandelt werden kann. Obwohl jede Variable vom Referenztyp ein Bitmuster enthält, gibt es keine feste Beziehung zwischen den in solchen Variablen gespeicherten Bitmustern und den von ihnen identifizierten Objekten. Es gibt keine Möglichkeit, dass Code Informationen aus einem Objekt oder einer Referenz darauf extrahieren und später bestimmen kann, ob eine andere Referenz dasselbe Objekt identifiziert, es sei denn, der Code enthielt oder wusste von einer Referenz, die das ursprüngliche Objekt identifizierte.

Superkatze
quelle
5

Das ist wie ein Zeiger auf einen Zeiger in C. In .NET vorbei dies ermöglicht es Ihnen zu ändern , was die ursprüngliche T bezieht sich auf, persönlich , obwohl ich denke , wenn Sie tun , dass in .NET Sie haben wahrscheinlich ein Design - Problem bekommen!

pezi_pink_squirrel
quelle
3

Indem Sie das refSchlüsselwort mit Referenztypen verwenden, übergeben Sie effektiv eine Referenz an die Referenz. In vielerlei Hinsicht ist es dasselbe wie die Verwendung des outSchlüsselworts, aber mit dem kleinen Unterschied, dass es keine Garantie gibt, dass die Methode dem refParameter 'ed tatsächlich etwas zuweist.

Isak Savo
quelle
3

ref ahmt (oder verhält sich) als globaler Bereich nur für zwei Bereiche nach:

  • Anrufer
  • Angerufene.
Guneysos
quelle
1

Wenn Sie jedoch einen Wert übergeben, sind die Dinge anders. Sie können die Übergabe eines Werts als Referenz erzwingen. Auf diese Weise können Sie beispielsweise eine Ganzzahl an eine Methode übergeben und die Ganzzahl in Ihrem Namen von der Methode ändern lassen.

Andrew
quelle
4
Unabhängig davon, ob Sie eine Referenz oder einen Wert vom Werttyp übergeben, wird standardmäßig ein Wert übergeben. Sie müssen nur die Typen mit Bezug zu verstehen, der Wert , den Sie Gang ist eine Referenz. Dies ist nicht die gleiche wie vorbei durch Referenz.
Jon Skeet
1

Ref gibt an, ob die Funktion das Objekt selbst oder nur seinen Wert in die Hände bekommen kann.

Das Übergeben als Referenz ist nicht an eine Sprache gebunden; Es handelt sich um eine Parameterbindungsstrategie neben Wertübergabe, Namensübergabe, Bedarfsübergabe usw.

Eine Nebenbemerkung: Der Klassenname TestRefist in diesem Zusammenhang eine schrecklich schlechte Wahl;).

xtofl
quelle