2016-11-12 3 views
4

Ich bin ein Anfänger für C# -Programmierung. Ich studiere jetzt strings, structs, value types und reference types. Als akzeptierte Antworten in here und in here, strings sind Referenztypen, die Zeiger auf Stapel gespeichert haben, während ihre tatsächlichen Inhalte auf Heap gespeichert sind. Wie in here beansprucht, sind structs Werttypen. Jetzt versuche ich mit structs und strings mit einem kleinen Beispiel zu üben:Warum verhalten sich Verweistypen innerhalb von Strukturen wie Werttypen?

struct Person 
{ 
    public string name; 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     Person person_1 = new Person(); 
     person_1.name = "Person 1"; 

     Person person_2 = person_1; 
     person_2.name = "Person 2"; 

     Console.WriteLine(person_1.name); 
     Console.WriteLine(person_2.name); 
    } 
} 

Der obige Code-Schnipsel Ausgänge

Person 1 
Person 2 

, die mich verwirrt macht. Wenn strings Referenztypen sind und structs Werttypen sind, dann sollten person_1.name und person_2.name auf die gleiche Space-Region auf dem Heap zeigen, sollten sie nicht?

+0

Ich bin ... verwirrt aswell ... Sie setzen buchstäblich die Werte der Namen, richtig? Was ist dann falsch mit dem Ergebnis? –

+2

@IanH OP verwirrend, wenn die Referenz mutiert wird (wodurch sie sich vollständig auf ein anderes Objekt bezieht), wenn das Objekt, auf das sich die Referenz bezieht, mutiert wird. String ist ein schlechtes Beispiel, weil es unveränderlich ist. –

+0

@RaymondChen Ah, ich denke, ich bekomme es wissen, danke :) –

Antwort

8

Strings sind Referenztypen, die Zeiger gespeichert auf dem Stapel, während ihr eigentlicher Inhalt auf Heap gespeichert

Nein nein nein. Denken Sie zuerst an Stapel und Heap. Das ist fast immer der falsche Weg, um in C# zu denken. C# verwaltet die Speicherlebensdauer für Sie.

Zweitens, obwohl Referenzen als Zeiger implementiert werden können, sind Referenzen keine logischen Zeiger. Referenzen sind Referenzen. C# hat Referenzen und Zeiger. Verwechsle sie nicht. In C# gibt es keinen Zeiger auf eine Zeichenfolge. Es gibt Verweise auf string.

Drittens könnte ein Verweis auf eine Zeichenfolge auf dem Stapel gespeichert werden, aber es könnte auch auf dem Heap gespeichert werden. Wenn Sie ein Array mit Verweisen auf string haben, befinden sich die Array-Inhalte auf dem Heap.

Jetzt kommen wir zu Ihrer eigentlichen Frage.

Person person_1 = new Person(); 
    person_1.name = "Person 1"; 
    Person person_2 = person_1; // This is the interesting line 
    person_2.name = "Person 2"; 

Lassen Sie uns veranschaulichen, was der Code logisch tut.Ihre Person struct ist nichts anderes als eine String-Referenz, so dass Ihr Programm ist das gleiche wie:

string person_1_name = null; // That's what new does on a struct 
person_1_name = "Person 1"; 
string person_2_name = person_1_name; // Now they refer to the same string 
person_2_name = "Person 2"; // And now they refer to different strings 

Wenn Sie sagen, person2 = person1 das bedeutet nicht, dass der Variable person1 ist nun ein Alias ​​für die Variable person2. (Es gibt eine Möglichkeit, das in C# zu tun, aber das ist es nicht.) Es bedeutet "kopiere den Inhalt von person1 to person2". Der Verweis auf die Zeichenfolge ist der Wert, der kopiert wird.

Wenn das nicht klar ist, probiere Zeichenkästen für Variablen und Pfeile für Referenzen; Wenn die Struktur kopiert wird, wird eine Kopie des Pfeils erstellt, keine Kopie der Box.

+0

Es ist klar. Vielen Dank! –

7

Jede Strukturinstanz hat eigene Felder. person_1.name ist eine unabhängige Variable von person_2.name. Diese sind nichtstatic Felder.

kopiert die Struktur nach Wert.

Die Tatsache, dass string unveränderlich ist, ist nicht erforderlich, um dieses Verhalten zu erklären.

Hier ist der gleiche Fall mit einem class stattdessen den Unterschied zu demonstrieren:

class C { public string S; } 

C c1 = new C(); 
C c2 = c1; //copy reference, share object 
c1.S = "x"; //it appears that c2.S has been set simultaneously because it's the same object 

Hier c1.S und c2.S auf die gleiche Variable verweisen. Wenn Sie dies zu einem struct machen, werden sie zu verschiedenen Variablen (wie in Ihrem Code). c2 = c1 dann eine Kopie des Strukturwerts, wo es zuvor eine Kopie einer Objektreferenz war.

+1

Vielleicht erwähnenswert, dass Strukturen durch Zuweisung kopiert werden (und in anderen ähnlichen Szenarien), im Gegensatz zu Referenztypen. – Evk

+0

Ich bin verwirrt. Wie wäre das Verhalten, wenn "Person" eine Klasse wäre? Ich würde erwarten, dass die Ausgabe "Person 2 Person 2" ist. – InBetween

+0

@InBetween das ist eigentlich richtig. Ich verstehe nicht, warum diese Antwort 4-mal upvoted wurde, dann :) Ich habe seinen Code falsch gelesen und angenommen, dass er 'new' für jede Variable verwendet. Ich mag es wirklich nicht, dass die Rep-Nummer neben dem Benutzernamen blinde Upvotes verursacht. – usr

5

Der beste Weg, dies zu verstehen, ist zu verstehen, was Variablen sind; Variablen sind, einfach gesagt, Platzhalter, die Werte halten.

Also was genau ist dieser Wert? Bei einem Referenztyp ist der in der Variablen gespeicherte Wert die Referenz (die Adresse sozusagen) zu einem gegebenen Objekt. Bei einem Werttyp ist der Wert das Objekt selbst.

Wenn Sie AnyType y = x; tun, was wirklich passiert ist, dass eine Kopie des gespeicherten Wertes in x hergestellt und dann in y gespeichert.

Wenn also x ein Referenztyp ist, zeigen sowohl x als auch y auf dasselbe Objekt, da beide identische Kopien der gleichen Referenz enthalten. Wenn x ein Werttyp ist, dann werden sowohl x als auch y zwei identische, aber verschiedene Objekte enthalten.

Sobald Sie das verstanden haben, sollte es Sinn machen, warum Ihr Code sich so verhält wie er.Lets study es Schritt für Schritt:

Person person_1 = new Person(); 

Ok wir erstellen eine neue Instanz eines Werttyps. Nach dem, was ich zuvor erklärt habe, speichert der Wert in person_1 das neu erstellte Objekt selbst. Wenn dieser Wert gespeichert wird (Heap oder Stack), handelt es sich um ein Implementierungsdetail, das überhaupt nicht relevant ist, wie sich der Code verhält.

person_1.name = "Person 1"; 

Jetzt setzen wir die Variable name, die ein Feld von person_1 sein geschieht. Der Wert von name ist wiederum eine Referenz, die auf irgendwo im Speicher zeigt, wo die string "Person 1" gespeichert ist. Auch hier ist der Wert oder die Zeichenfolge irrelevant.

Person person_2 = person_1; 

Ok, das ist der interessante Teil. was geschieht hier? Nun, eine Kopie des in person_1 gespeicherten Wertes wird gemacht und in person_2 gespeichert. Da der Wert eine Instanz eines Werttyps ist, wird eine neue Kopie dieser Instanz erstellt und in person_2 gespeichert. Diese neue Kopie hat ihr eigenes Feld name und der in dieser Variablen gespeicherte Wert ist wiederum eine Kopie des in person_1.name gespeicherten Wertes (eine Referenz auf "Person 1").

person_2.name = "Person 2"; 

Nun werden wir einfach die Variable person_2.name neu zuweisen. Dies bedeutet, dass wir eine neue Referenz speichern, die auf eine neue string irgendwo im Speicher verweist. Zu beachten ist, dass person_2.name ursprünglich ein kopieren des Wertes gespeichert in person_1.name so gehalten, was auch immer Sie tun, um person_2.name keine Wirkung auf den Wert, in person_1.name gespeichert wird, weil Sie einfach ändern ... ja genau, ein kopieren. Und deshalb verhält sich Ihr Code so, wie er es tut.

Als Übung versuchen Sie auf ähnliche Weise, wie sich Ihr Code verhalten würde, wenn Person ein Referenztyp wäre.

3

Denken Sie an Strings sind Arrays von Zeichen. Der folgende Code ist ähnlich wie bei Ihnen, aber mit Arrays.

public struct Lottery 
{ 
    public int[] numbers; 
} 

public static void Main() 
{ 
    var A = new Lottery(); 
    A.numbers = new[] { 1,2,3,4,5 }; 
    // struct A is in the stack, and it contains one reference to an array in RAM 

    var B = A; 
    // struct B also is in the stack, and it contains a copy of A.numbers reference 
    B.numbers[0] = 10; 
    // A.numbers[0] == 10, since both A.numbers and B.numbers point to same memory 
    // You can't do this with strings because they are immutable 

    B.numbers = new int[] { 6,7,8,9,10 }; 
    // B.numbers now points to a new location in RAM 
    B.numbers[0] = 60; 
    // A.numbers[0] == 10, B.numbers[0] == 60   
    // The two structures A and B *are completely separate* now. 
} 

Wenn Sie also eine Struktur haben, die Verweise (Strings, Arrays oder Klassen) enthält, und Sie möchten implementieren ICloneable sicherstellen, dass Sie auch den Inhalt der Referenzen klonen.

public class Person : ICloneable 
{ 
    public string Name { get; set; } 

    public Person Clone() 
    { 
     return new Person() { Name=this.Name }; // string copy 
    } 
    object ICloneable.Clone() { return Clone(); } // interface calls specific function 
} 
public struct Project : ICloneable 
{ 
    public Person Leader { get; set; } 
    public string Name { get; set; } 
    public int[] Steps { get; set; } 

    public Project Clone() 
    { 
     return new Project() 
     { 
      Leader=this.Leader.Clone(),   // calls Clone for copy 
      Name=this.Name,      // string copy 
      Steps=this.Steps.Clone() as int[] // shallow copy of array 
     }; 
    } 
    object ICloneable.Clone() { return Clone(); } // interface calls specific function 
} 
Verwandte Themen