Wie werden Zeichenfolgen in .NET übergeben?

120

Wenn ich a stringan eine Funktion übergebe, wird ein Zeiger auf den Inhalt der Zeichenfolge übergeben, oder wird die gesamte Zeichenfolge wie a an die Funktion auf dem Stapel übergeben struct?

Cole Johnson
quelle

Antworten:

277

Eine Referenz wird übergeben; Es ist jedoch technisch nicht als Referenz übergeben. Dies ist eine subtile, aber sehr wichtige Unterscheidung. Betrachten Sie den folgenden Code:

void DoSomething(string strLocal)
{
    strLocal = "local";
}
void Main()
{
    string strMain = "main";
    DoSomething(strMain);
    Console.WriteLine(strMain); // What gets printed?
}

Es gibt drei Dinge, die Sie wissen müssen, um zu verstehen, was hier passiert:

  1. Zeichenfolgen sind Referenztypen in C #.
  2. Sie sind auch unveränderlich. Wenn Sie also etwas tun, das aussieht, als würden Sie die Zeichenfolge ändern, sind Sie es nicht. Eine völlig neue Zeichenfolge wird erstellt, die Referenz wird darauf gerichtet und die alte wird weggeworfen.
  3. Obwohl Zeichenfolgen Referenztypen sind, strMainwerden sie nicht als Referenz übergeben. Es ist ein Referenztyp, aber die Referenz selbst wird als Wert übergeben . Jedes Mal, wenn Sie einen Parameter ohne das refSchlüsselwort übergeben (ohne outParameter), haben Sie etwas nach Wert übergeben.

Das muss also bedeuten, dass Sie ... eine Referenz nach Wert übergeben. Da es sich um einen Referenztyp handelt, wurde nur die Referenz auf den Stapel kopiert. Aber was heißt das?

Referenztypen nach Wert übergeben: Sie tun es bereits

C # -Variablen sind entweder Referenztypen oder Werttypen . C # -Parameter werden entweder als Referenz oder als Wert übergeben . Terminologie ist hier ein Problem; diese klingen wie das gleiche, sind es aber nicht.

Wenn Sie einen Parameter vom Typ ANY übergeben und das refSchlüsselwort nicht verwenden , haben Sie es als Wert übergeben. Wenn Sie es als Wert übergeben haben, haben Sie wirklich eine Kopie übergeben. Wenn der Parameter jedoch ein Referenztyp war, war das, was Sie kopiert haben, die Referenz, nicht das, worauf sie zeigte.

Hier ist die erste Zeile der MainMethode:

string strMain = "main";

In dieser Zeile haben wir zwei Dinge erstellt: eine Zeichenfolge, deren Wert mainirgendwo im Speicher gespeichert ist, und eine Referenzvariable, die strMaindarauf verweist.

DoSomething(strMain);

Nun geben wir diesen Hinweis an weiter DoSomething. Wir haben es als Wert übergeben, das heißt, wir haben eine Kopie erstellt. Es ist ein Referenztyp, das heißt, wir haben die Referenz kopiert, nicht die Zeichenfolge selbst. Jetzt haben wir zwei Referenzen, die jeweils auf denselben Wert im Speicher verweisen.

Im Angerufenen

Hier ist die Spitze der DoSomethingMethode:

void DoSomething(string strLocal)

Kein refSchlüsselwort, so strLocalund strMainsind zwei verschiedene Verweise auf den gleichen Wert zeigen. Wenn wir neu zuweisen strLocal...

strLocal = "local";   

... wir haben den gespeicherten Wert nicht geändert; Wir haben die aufgerufene Referenz genommen strLocalund sie auf eine brandneue Saite gerichtet. Was passiert, strMainwenn wir das tun? Nichts. Es zeigt immer noch auf die alte Saite.

string strMain = "main";    // Store a string, create a reference to it
DoSomething(strMain);       // Reference gets copied, copy gets re-pointed
Console.WriteLine(strMain); // The original string is still "main" 

Unveränderlichkeit

Lassen Sie uns das Szenario für eine Sekunde ändern. Stellen Sie sich vor, wir arbeiten nicht mit Zeichenfolgen, sondern mit einem veränderlichen Referenztyp, wie einer von Ihnen erstellten Klasse.

class MutableThing
{
    public int ChangeMe { get; set; }
}

Wenn Sie dem Verweis auf das Objekt folgen,objLocal auf das es zeigt, können Sie seine Eigenschaften ändern:

void DoSomething(MutableThing objLocal)
{
     objLocal.ChangeMe = 0;
} 

Es ist immer noch nur eine MutableThingim Speicher, und sowohl die kopierte Referenz als auch die ursprüngliche Referenz zeigen immer noch darauf. Die Eigenschaften des MutableThingselbst haben sich geändert :

void Main()
{
    var objMain = new MutableThing();
    objMain.ChangeMe = 5; 
    Console.WriteLine(objMain.ChangeMe); // it's 5 on objMain

    DoSomething(objMain);                // now it's 0 on objLocal
    Console.WriteLine(objMain.ChangeMe); // it's also 0 on objMain   
}

Ah, aber Saiten sind unveränderlich! Es ist keine ChangeMeEigenschaft festzulegen. strLocal[3] = 'H'In C # können Sie dies nicht wie mit einem charArray im C-Stil tun . Sie müssen stattdessen eine ganz neue Zeichenfolge erstellen. Die einzige Möglichkeit zum Ändern strLocalbesteht darin, die Referenz auf eine andere Zeichenfolge zu verweisen. Dies bedeutet, dass nichts, was Sie tun, strLocalAuswirkungen haben kann strMain. Der Wert ist unveränderlich und die Referenz ist eine Kopie.

Referenz als Referenz übergeben

Um zu beweisen, dass es einen Unterschied gibt, geschieht Folgendes, wenn Sie eine Referenz als Referenz übergeben:

void DoSomethingByReference(ref string strLocal)
{
    strLocal = "local";
}
void Main()
{
    string strMain = "main";
    DoSomethingByReference(ref strMain);
    Console.WriteLine(strMain);          // Prints "local"
}

Dieses Mal wird die Zeichenfolge in Mainwirklich geändert, da Sie die Referenz übergeben haben, ohne sie auf den Stapel zu kopieren.

Obwohl Zeichenfolgen Referenztypen sind, bedeutet die Übergabe als Wert, dass alles, was im Angerufenen vor sich geht, die Zeichenfolge im Aufrufer nicht beeinflusst. Da es sich jedoch um Referenztypen handelt, müssen Sie nicht die gesamte Zeichenfolge in den Speicher kopieren, wenn Sie sie weitergeben möchten.

Weitere Ressourcen:

Justin Morgan
quelle
3
@TheLight - Entschuldigung, aber Sie sind hier falsch, wenn Sie sagen: "Ein Referenztyp wird standardmäßig als Referenz übergeben." Standardmäßig werden alle Parameter als Wert übergeben. Bei Referenztypen bedeutet dies jedoch, dass die Referenz als Wert übergeben wird. Sie kombinieren Referenztypen mit Referenzparametern, was verständlich ist, da es sich um eine sehr verwirrende Unterscheidung handelt. Weitere Informationen finden Sie hier im Abschnitt Übergeben von Referenztypen nach Wert. Ihr verlinkter Artikel ist ganz richtig, aber er unterstützt tatsächlich meinen Standpunkt.
Justin Morgan
1
@JustinMorgan Nicht um einen toten Kommentarthread aufzurufen, aber ich denke, TheLights Kommentar ist sinnvoll, wenn Sie in C denken. In C sind Daten nur ein Speicherblock. Eine Referenz ist ein Zeiger auf diesen Speicherblock. Wenn Sie den gesamten Speicherblock an eine Funktion übergeben, wird dies als "Übergabe nach Wert" bezeichnet. Wenn Sie den Zeiger übergeben, wird er als "Referenzübergabe" bezeichnet. In C # gibt es keine Vorstellung davon, den gesamten Speicherblock zu übergeben, daher haben sie "Übergeben nach Wert" neu definiert, um den Zeiger zu übergeben. Das scheint falsch zu sein, aber ein Zeiger ist auch nur ein Speicherblock! Für mich ist die Terminologie ziemlich willkürlich
21:29 Uhr
@roliu - Das Problem ist, dass wir nicht in C arbeiten und C # trotz seines ähnlichen Namens und seiner ähnlichen Syntax sehr unterschiedlich ist. Zum einen sind Referenzen nicht dasselbe wie Zeiger , und wenn man sie so betrachtet, kann dies zu Fallstricken führen. Das größte Problem ist jedoch, dass "Referenzübergabe" in C # eine sehr spezifische Bedeutung hat, für die das refSchlüsselwort erforderlich ist . Um zu beweisen, dass das Übergeben von Referenzen einen Unterschied macht, sehen Sie sich diese Demo an: rextester.com/WKBG5978
Justin Morgan
1
@JustinMorgan Ich stimme zu, dass das Mischen von C- und C # -Terminologie schlecht ist, aber obwohl ich Lipperts Beitrag genossen habe, stimme ich nicht zu, dass das Denken an Referenzen als Zeiger hier etwas besonders beschmutzt. Der Blog-Beitrag beschreibt, wie das Denken an eine Referenz als Zeiger zu viel Kraft verleiht. Ich bin mir bewusst, dass das refSchlüsselwort nützlich ist. Ich habe nur versucht zu erklären, warum man daran denken könnte , einen Referenztyp in C # als Wert zu übergeben. Dies scheint der "traditionelle" (dh C) Begriff zu sein, Referenz zu übergeben (und einen Referenztyp zu übergeben) als Referenz in C # scheint eher so zu sein, als würde eine Referenz auf eine Referenz nach Wert übergeben).
Rliu
2
Sie haben Recht, aber ich denke, @roliu bezog sich darauf, wie eine Funktion wie " während " ( oder in C ++) Foo(string bar)gedacht werden könnte . Sicher, so sollte man es nicht jeden Tag denken, aber es hat mir tatsächlich geholfen, endlich zu verstehen, was unter der Haube passiert. Foo(char* bar)Foo(ref string bar)Foo(char** bar)Foo(char*& bar)Foo(string& bar)
Cole Johnson
23

Zeichenfolgen in C # sind unveränderliche Referenzobjekte. Dies bedeutet, dass Verweise auf sie (nach Wert) weitergegeben werden. Sobald eine Zeichenfolge erstellt wurde, können Sie sie nicht mehr ändern. Methoden , die Versionen des Strings (Strings, getrimmte Versionen, etc.) erstellen modifizierten modifizierte produzieren Kopien der Original - Zeichenkette.

dasblinkenlight
quelle
10

Saiten sind Sonderfälle. Jede Instanz ist unveränderlich. Wenn Sie den Wert einer Zeichenfolge ändern, weisen Sie eine neue Zeichenfolge im Speicher zu.

Es wird also nur die Referenz an Ihre Funktion übergeben, aber wenn die Zeichenfolge bearbeitet wird, wird sie zu einer neuen Instanz und ändert die alte Instanz nicht.

Rätselhaftigkeit
quelle
4
Strings sind in dieser Hinsicht kein Sonderfall. Es ist sehr einfach, unveränderliche Objekte zu erstellen, die dieselbe Semantik haben könnten. (Das heißt, eine Instanz eines Typs, die keine Methode zum
Zeichenfolgen sind Sonderfälle - sie sind effektiv unveränderliche Referenztypen, die insofern veränderlich erscheinen, als sie sich wie Werttypen verhalten.
Rätselhaftigkeit
1
@Enigmativity Nach dieser Logik sind dann Uri(Klasse) und Guid(Struktur) auch Sonderfälle. Ich sehe nicht mehr System.String, wie sich ein "Werttyp" verhält als andere unveränderliche Typen ... entweder von Klassen- oder Strukturursprüngen.
3
@pst - Strings haben eine spezielle Erstellungssemantik - im Gegensatz zu Uri& Guid- können Sie einer Stringvariablen einfach einen String-Literal-Wert zuweisen. Die Zeichenfolge scheint veränderbar zu sein, wie eine intNeuzuweisung, aber sie erstellt implizit ein Objekt - kein newSchlüsselwort.
Rätselhaftigkeit
3
String ist ein Sonderfall, der jedoch für diese Frage keine Relevanz hat. Werttyp, Referenztyp, welcher Typ auch immer, wird in dieser Frage alle gleich handeln.
Kirk Broadhurst