2012-04-03 1 views
22

Ich dachte immer, dass ein Methodenparameter mit einem Klassentyp standardmäßig als Referenzparameter übergeben wird. Anscheinend ist das nicht immer der Fall. Betrachten Sie diese Komponententests in C# (mit MSTest).Das Übergeben einer Klasse als Ref-Parameter in C# funktioniert nicht immer wie erwartet. Kann mir jemand erklären?

[TestClass] 
public class Sandbox 
{ 
    private class TestRefClass 
    { 
     public int TestInt { get; set; } 
    } 

    private void TestDefaultMethod(TestRefClass testClass) 
    { 
     testClass.TestInt = 1; 
    } 

    private void TestAssignmentMethod(TestRefClass testClass) 
    { 
     testClass = new TestRefClass() { TestInt = 1 }; 
    } 

    private void TestAssignmentRefMethod(ref TestRefClass testClass) 
    { 
     testClass = new TestRefClass() { TestInt = 1 }; 
    } 

    [TestMethod] 
    public void DefaultTest() 
    { 
     var testObj = new TestRefClass() { TestInt = 0 }; 
     TestDefaultMethod(testObj); 
     Assert.IsTrue(testObj.TestInt == 1); 
    } 

    [TestMethod] 
    public void AssignmentTest() 
    { 
     var testObj = new TestRefClass() { TestInt = 0 }; 
     TestAssignmentMethod(testObj); 
     Assert.IsTrue(testObj.TestInt == 1); 
    } 

    [TestMethod] 
    public void AssignmentRefTest() 
    { 
     var testObj = new TestRefClass() { TestInt = 0 }; 
     TestAssignmentRefMethod(ref testObj); 
     Assert.IsTrue(testObj.TestInt == 1); 
    } 
} 

Die Ergebnisse sind, dass AssignmentTest() ausfällt und die beiden anderen Testmethoden übergeben. Ich nehme an, das Problem ist, dass das Zuweisen einer neuen Instanz zu dem Parameter testClass den Parameterverweis unterbricht, aber das Hinzufügen des Schlüsselworts ref irgendwie das Problem behebt.

Kann jemand eine gute, detaillierte Erklärung geben, was hier vor sich geht? Ich versuche hauptsächlich mein Wissen über C# zu erweitern; Ich habe kein bestimmtes Szenario, das ich versuche zu lösen ...

Antwort

29

Die Sache, die ist fast immer vergessen ist, dass eine Klasse nicht als Referenz übergeben wird, der Verweis auf die Klasse wird als Wert übergeben.

Dies ist wichtig. Anstatt die gesamte Klasse zu kopieren (Wert im stereotypen Sinn), wird die Referenz in diese Klasse kopiert (ich vermeide es, "Zeiger" zu sagen). Dies ist 4 oder 8 Bytes; viel schmackhafter als das Kopieren der ganzen Klasse und bedeutet in Wirklichkeit, dass die Klasse "durch Verweis" weitergegeben wird.

An dieser Stelle hat die Methode eine eigene Kopie der Referenz auf die Klasse. Zuweisung bis Diese Referenz wird innerhalb der Methode definiert (die Methode hat nur ihre eigene Kopie der Referenz neu zugewiesen).

Dereferenzierung dieser Referenz (wie im Gespräch mit Klassenmitgliedern) würde wie erwartet funktionieren: Sie würden die zugrunde liegende Klasse sehen, wenn Sie sie nicht ändern, um nach einer neuen Instanz zu suchen (was Sie bei Ihrem Fehler tun) Prüfung).

Verwenden Sie das ref Schlüsselwort ist effektiv übergeben die Referenz selbst durch Verweis (Zeiger auf einen Zeiger Art der Sache).

Wie immer Jon Skeet eine sehr gut geschrieben Übersicht zur Verfügung gestellt hat:

http://www.yoda.arachsys.com/csharp/parameters.html

Achten Sie auf den Teil "Referenzparameter":

Referenzparameter nicht passieren die Werte der Variablen, die in der Funktion member Aufruf verwendet werden - sie verwenden die Variablen selbst.

Wenn die Methode, etwas zu einem ref Referenz zuweist, dann die Kopie des Anrufers betroffen auch (wie Sie bemerkt haben), weil sie an der gleichen Verweis auf eine Instanz im Speicher suchen (im Gegensatz zu jeder hat seine eigene Kopie).

4

Die Standardkonvention für Parameter in C# wird als Wert übergeben. Dies gilt unabhängig davon, ob der Parameter class oder struct ist. In dem Fall class wird nur die Referenz als Wert übergeben, während im Fall struct eine flache Kopie des gesamten Objekts übergeben wird.

Bei der Eingabe der TestAssignmentMethod gibt es zwei Verweise auf ein einzelnes Objekt: testObj, die in AssignmentTest und testClass lebt, die in TestAssignmentMethod lebt. Wenn Sie das tatsächliche Objekt über testClass oder testObj mutieren würden, wäre es für beide Referenzen sichtbar, da beide auf das gleiche Objekt zeigen. Dies erzeugt ein neues Objekt in der ersten Zeile, obwohl Sie

testClass = new TestRefClass() { TestInt = 1 } 

ausführen und weist testClass darauf. Dies ändert nichts an der Stelle, wo der testObj Verweis in irgendeiner Weise zeigt, weil testClass eine unabhängige Kopie ist. Es gibt nun 2 Objekte und 2 Referenzen, die jeweils auf eine andere Objektinstanz verweisen.

Wenn Sie die Referenz Semantik übergeben möchten, müssen Sie einen ref Parameter verwenden.

+1

Schöne, detaillierte Antwort, aber aus irgendeinem Grund finde ich es immer irritierend, wenn jemand sagt "Parameter werden immer nach Wert übergeben, auch wenn es eine Referenz ist, die du passierst." Ich sehe nicht, wie solch eine pedantische Unterscheidung leuchtet. –

+0

@RobertHarvey Irritierend, aber leider im Standardfall immer wahr (außer natürlich, 'ref' oder' out' sind anwesend). Ich glaube, die beste Erklärung, die ich je gelesen habe, stammt von Jon Skeet in seinem C# -Buch. –

+0

@RobertHarvey die Terminologie ist leider verwirrend :( – JaredPar

2

Die AssignmentTest verwendet TestAssignmentMethod die ändert nur dieObjektreferenz Wert übergeben.

So wird das Objekt selbst als Referenz übergeben, aber die Referenz auf das Objekt wird als Wert übergeben. so, wenn Sie tun:

testClass = new TestRefClass() { TestInt = 1 }; 

Sie die lokale kopiert Bezug auf die Methode übergeben ändert nicht die Referenz, die Sie in dem Test haben.

Also hier:

[TestMethod] 
public void AssignmentTest() 
{ 
    var testObj = new TestRefClass() { TestInt = 0 }; 
    TestAssignmentMethod(testObj); 
    Assert.IsTrue(testObj.TestInt == 1); 
} 

testObj ist eine Referenzgröße. Wenn Sie es an TestAssignmentMethod(testObj); übergeben, wird die Referenz als Wert übergeben. Wenn Sie es also in der Methode ändern, zeigt Originalreferenz immer noch auf das gleiche Objekt.

2

Meine 2 Cent

Wenn Klasse eine Methode übergeben wird eine Kopie davon der Speicherplatz-Adresse gesendet wird (eine Richtung Sie Haus gesendet werden). Also jede Operation an dieser Adresse mit Auswirkungen auf das Haus, aber wird nicht die Adresse selbst ändern. (Dies ist der Standardwert)

Das Übergeben der Klasse (Objekt) durch Referenz bewirkt, dass die tatsächliche Adresse anstelle der Kopie einer Adresse übergeben wird. Das heißt, wenn Sie einem Argument ein neues Objekt zuweisen, ändert sich die tatsächliche Adresse (ähnlich wie bei der Verschiebung). : D

So sehe ich es.