Referenztyp in C #

78

Betrachten Sie diesen Code:

public class Program
{
    private static void Main(string[] args)
    {
        var person1 = new Person { Name = "Test" };
        Console.WriteLine(person1.Name);

        Person person2 = person1;
        person2.Name = "Shahrooz";
        Console.WriteLine(person1.Name); //Output: Shahrooz
        person2 = null;
        Console.WriteLine(person1.Name); //Output: Shahrooz
    }
}

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

Natürlich, wenn die Zuordnung person1zu person2und die NameEigenschaft person2geändert wird , die Namevon person1ebenfalls wird geändert. person1und person2haben die gleiche Referenz.

Warum ist person2 = nulldie person1Variable dann auch nicht null?

Keine Idee für den Namen
quelle

Antworten:

182

Beide personund person2sind Verweise auf dasselbe Objekt. Dies sind jedoch unterschiedliche Referenzen. Also, wenn du rennst

person2 = null;

Sie ändern nur die Referenz person2und lassen die Referenz personund das entsprechende Objekt unverändert.

Ich denke, der beste Weg, dies zu erklären, ist eine vereinfachte Darstellung. So sah die Situation vorher aus person2 = null :

Vor der Nullzuweisung

Und hier ist das Bild nach der Nullzuweisung:

Geben Sie hier die Bildbeschreibung ein

Wie Sie sehen können, person2verweist das zweite Bild auf nichts (oder nullgenau genommen, da Verweis auf nichts und Verweis auf nullunterschiedliche Bedingungen, siehe Kommentar von Rune FS ), während personauf ein vorhandenes Objekt verwiesen wird.

Andrei
quelle
22
Mit anderen Worten, person und person2 zeigen auf etwas, dann zeigt person2 auf nichts, indem sie auf null gesetzt werden.
Rro
2
@ShahroozJefri ㇱ Grundsätzlich person1und person2gibt 2 verschiedene Visitenkarten , die eine Adresse enthalten. Wenn Sie das tun , person1 = person2dann person1ist jetzt eine Kopie person2. Es handelt sich immer noch um unterschiedliche Visitenkarten, aber beide enthalten dieselbe Adresse, die auf dasselbe Objekt zeigt. Das Objekt selbst ändert sich nicht.
Nolonar
Ich würde den Pfeil person2im zweiten Bild entfernen, weil er auf nichts verweist - es ist keine baumelnde Referenz.
Sameer Singh
@SameerSingh, fertig, danke für die Antwort. Anfangs dachte ich das Gleiche, überlegte es mir aber aus irgendeinem Grund anders.
Andrei
2
Wenn Sie Referenzen verwenden, eine Variable erstellen und einem anderen Zeiger zuweisen, wird ein Objekt namens Alias ​​erstellt, bei dem beispielsweise 3 Variablen auf denselben Speicherort verweisen. Wenn Sie eine dieser Variablen aufheben, zeigt eine Variable weniger auf diesen Speicherort. Wenn alle Variablen auf NULL zeigen, bereinigt der GC diesen Speicherort, um neue Variablen zuzuweisen. Zumindest in Java funktioniert der GC folgendermaßen. D:
NemesisDoom
55

Betrachten Sie person1und person2 als Zeiger auf einen Speicherort. Im ersten Schritt wird nur person1die Adresse des Objekts aus dem Speicher und später person2die Adresse des Speicherorts des Objekts aus dem Speicher gespeichert. Wenn Sie später zuordnen nullzu person2, person1bleibt hiervon unberührt. Deshalb sehen Sie das Ergebnis.

Sie können lesen: Wert vs Referenztypen von Joseph Albahari

Bei Referenztypen wird jedoch ein Objekt im Speicher erstellt und dann über eine separate Referenz behandelt - ähnlich wie ein Zeiger.

Ich werde versuchen, dasselbe Konzept anhand des folgenden Diagramms darzustellen.

Geben Sie hier die Bildbeschreibung ein

Erstellt ein neues Objekt vom Typ Person und die person1Referenz (Zeiger) zeigt auf den Speicherort im Speicher.

Geben Sie hier die Bildbeschreibung ein

Erstellt eine neue Referenz (Zeiger) person2, die im Speicher auf dieselbe verweist.

Geben Sie hier die Bildbeschreibung ein

Die Objekteigenschaft Name wurde in einen neuen Wert geändert, person2da beide Referenzen auf dasselbe Objekt verweisen und Console.WriteLine(person1.Name);ausgegeben werden Shahrooz.

Geben Sie hier die Bildbeschreibung ein

Nach dem Zuweisen nullzur person2Referenz zeigt es auf nichts, hält aber person1immer noch die Referenz auf das Objekt.

(Schließlich sollten Sie für die Speicherverwaltung sehen, dass der Stapel ein Implementierungsdetail ist, Teil 1, und der Stapel ist ein Implementierungsdetail, Teil 2 von Eric Lippert.)

Habib
quelle
3
Jeder Verweis auf "den Heap" ist ein irrelevantes Implementierungsdetail. Ich bin mir nicht mal sicher, ob es stimmt, es muss auch nicht sein.
Jmoreno
@jmoreno, deshalb folgte der letzte Absatz, eigentlich folgte ich dem Stil in Joseph Albaharis Artikel, der oben auf der Antwort verlinkt ist
Habib
4
@jmoreno, was diese Antwort betrifft, ist es richtig, dass das Objekt vom Typ personauf dem Heap und seinen Referenzen gespeichert person1und person2auf dem Stapel wäre. Aber ich stimme zu, dass es eher ein Implementierungsdetail ist. Aber Stapel und Haufen in der Antwort zu haben (wie Albaharis Artikel) würde es IMO klarer machen.
Habib
1
@jmoreno und Habin: Ich habe die Verwendung von Heap durch Speicher ersetzt , da die Verwendung oder Nichtverwendung * des speziellen Speichermechanismus, der allgemein als Heap bekannt ist, irrelevant ist.
Pieter Geerkens
1
Tolle Antwort, aber es ist etwas übertrieben für die gestellte Frage.
Rasiermesser
14

Sie haben die person2Referenz geändert , verweisen dort nulljedoch person1nicht darauf.

Was ich meine ist, dass wenn wir uns die Aufgabe ansehen person2und person1vor ihr, dann beide auf dasselbe Objekt verweisen. Dann weisen Sie zu person2 = null, sodass Person 2 jetzt auf einen anderen Typ verweist. Das Objekt, auf person2das verwiesen wurde , wurde nicht gelöscht .

Ich habe dieses GIF erstellt, um es zu veranschaulichen:

Geben Sie hier die Bildbeschreibung ein

Keine Idee für den Namen
quelle
13

Weil Sie den Verweis auf gesetzt haben null.

Wenn Sie eine Referenz festlegen null, ist die Referenz selbst null.. nicht das Objekt, auf das sie verweist.

Stellen Sie sich diese als eine Variable vor, die einen Versatz von 0 enthält. Sie personhat den Wert 120. Sie person2hat den Wert 120. Die Daten am Versatz 120 sind das PersonObjekt. Wenn Sie dies tun:

person2 = null;

..sie sagen effektiv , person2 = 0;. Hat aber personnoch den Wert 120.

Simon Whitehead
quelle
Also haben Person 2 und Person nicht den gleichen Bezug?
Sie verweisen auf dasselbe Objekt. Sie sind jedoch separate Referenzen. Dies hängt von der copy-by-valueSemantik ab. Sie kopieren den Wert der Referenz.
Simon Whitehead
4

Beide personund person2 zeigen auf dasselbe Objekt. Wenn Sie also den Namen eines der beiden ändern, werden beide geändert (da sie auf dieselbe Struktur im Speicher verweisen).

Aber wenn Sie die Einstellung person2auf null, machen Sie person2in einen Null - Zeiger, so dass ist nicht darauf auf das gleiche Objekt wie personmehr. Es wird nichts mit dem Objekt selbst tun, um es zu zerstören, und da es personimmer noch auf das Objekt verweist, wird es auch nicht durch die Speicherbereinigung getötet.

Wenn Sie auch festlegen person = nullund keine anderen Verweise auf das Objekt haben, wird es schließlich vom Garbage Collector entfernt.

Øyvind Bråthen
quelle
2

person1und person2auf dieselbe Speicheradresse zeigen. Wenn Sie null setzen, nullen person2Sie die Referenz und nicht die Speicheradresse. Wenn Sie also person1weiter auf diese Speicheradresse verweisen, ist dies der Grund. Wenn Sie das Classs Personin ein ändern, ändert sich Structdas Verhalten.

pointnetdeveloper
quelle
Sie können auf dieselbe Speicheradresse (oder eine beliebige Adresse) verweisen oder nicht. Wichtig ist, dass sie dasselbe Objekt identifizieren . Bei einigen Arten von gleichzeitigen Garbage Collectors ist es möglich, dass während des Verschiebens eines Objekts einige Referenzen die alte Adresse enthalten, während andere die neue identifizieren. [Code, der in eine der Adressen schreibt, muss möglicherweise blockiert werden, bis alle Referenzen aktualisiert wurden Code, der Adressen vergleicht, müsste sehen, ob eine Adresse "aktuell" war und die andere nicht, und wenn ja, die "neue" Adresse finden, die der alten zugeordnet ist.]
Supercat
2

Ich finde es am hilfreichsten, sich Referenztypen als Objekt-IDs vorzustellen. Wenn eine Variable vom Klassentyp vorhanden ist Car, myCar = new Car();fordert die Anweisung das System auf, ein neues Auto zu erstellen und seine ID zu melden (sagen wir, es ist Objekt Nr. 57). es setzt dann "Objekt # 57" in die Variable myCar. Wenn man schreibt Car2 = myCar;, schreibt das "Objekt # 57" in die Variable Car2. Wenn man schreibt car2.Color = blue;, weist dies das System an, das durch Car2 identifizierte Auto (z. B. Objekt Nr. 57) zu finden und es blau zu lackieren.

Die einzigen Operationen, die direkt an Objekt-IDs ausgeführt werden, sind das Erstellen eines neuen Objekts und das Abrufen der ID, das Abrufen einer "leeren" ID (dh null), das Kopieren einer Objekt-ID in eine Variable oder einen Speicherort, der sie enthalten kann, und das Überprüfen, ob zwei Objekt-IDs stimmen überein (beziehen sich auf dasselbe Objekt). Alle anderen Anforderungen fordern das System auf, das Objekt zu finden, auf das sich eine ID bezieht, und auf dieses Objekt zu reagieren (ohne die Variable oder andere Entität zu beeinflussen, die die ID enthielt).

In vorhandenen Implementierungen von .NET enthalten Objektvariablen wahrscheinlich Zeiger auf Objekte, die auf einem durch Müll gesammelten Heap gespeichert sind. Dies ist jedoch ein nicht hilfreiches Implementierungsdetail, da zwischen einer Objektreferenz und anderen Zeigern ein kritischer Unterschied besteht. Es wird allgemein angenommen, dass ein Zeiger den Ort von etwas darstellt, das lange genug stehen bleibt, um bearbeitet zu werden. Objektreferenzen nicht. Ein Codeteil kann das SI-Register mit einem Verweis auf ein Objekt unter der Adresse 0x12345678 laden, es verwenden und dann unterbrochen werden, während der Garbage Collector das Objekt an die Adresse 0x23456789 verschiebt. Das würde sich wie eine Katastrophe anhören, aber der Müll wird die mit dem Code verknüpften Metadaten untersuchen und feststellen, dass der Code SI verwendet hat, um die Adresse des verwendeten Objekts zu speichern (dh 0x12345678). Stellen Sie fest, dass das Objekt mit 0x12345678 in 0x23456789 verschoben wurde, und aktualisieren Sie SI so, dass es 0x23456789 enthält, bevor es zurückgegeben wird. Beachten Sie, dass in diesem Szenario der in SI gespeicherte numerische Wert vom Garbage Collector geändert wurde, auf den jedoch verwiesen wurdedas gleiche Objekt vor dem Umzug und danach. Wenn vor dem Verschieben auf das 23.592ste Objekt verwiesen wurde, das seit dem Start des Programms erstellt wurde, wird dies danach fortgesetzt. Interessanterweise speichert .NET für die meisten Objekte keine eindeutige und unveränderliche Kennung. Bei zwei Snapshots des Arbeitsspeichers eines Programms kann nicht immer festgestellt werden, ob ein bestimmtes Objekt im ersten Snapshot im zweiten vorhanden ist oder ob alle Spuren davon aufgegeben und ein neues Objekt erstellt wurde, das zufällig so aussieht alle beobachtbaren Details.

Superkatze
quelle
1

person1 und person2 sind zwei separate Referenzen auf dem Stapel, die auf dasselbe Person-Objekt auf dem Heap verweisen.

Wenn Sie eine der Referenzen löschen, wird sie aus dem Stapel entfernt und zeigt nicht mehr auf das Personenobjekt auf dem Heap. Die andere Referenz bleibt erhalten und verweist weiterhin auf das vorhandene Personenobjekt auf dem Heap.

Sobald alle Verweise auf das Person-Objekt entfernt wurden, entfernt der Garbage Collector das Objekt schließlich aus dem Speicher.

Ryan Spears
quelle
1

Wenn Sie einen Referenztyp erstellen, wird tatsächlich eine Referenz kopiert, wobei alle Objekte auf denselben Speicherort verweisen. Wenn Sie jedoch Person2 = Null zugewiesen haben, hat dies keine Auswirkung, da person2 nur eine Kopie der Referenzperson ist und wir gerade eine Kopie gelöscht haben der Referenz .

Suraj Singh
quelle
1

Beachten Sie, dass Sie eine Wertsemantik erhalten können, indem Sie zu a wechseln struct.

public class Program
{
    static void Main()
    {
        var person1 = new Person { Name = "Test" };
        Console.WriteLine(person1.Name);

        Person person2 = person1;
        person2.Name = "Shahrooz";
        Console.WriteLine(person1.Name);//Output:Test
        Console.WriteLine(person2.Name);//Output:Shahrooz
        person2 = new Person{Name = "Test2"};
        Console.WriteLine(person2.Name);//Output:Test2

    }
}
public struct Person
{
    public string Name { get; set; }
}
Mark Hurd
quelle
0
public class Program
{
    private static void Main(string[] args)
    {
        var person = new Person {Name = "Test"};
        Console.WriteLine(person.Name);

        Person person2 = person;
        person2.Name = "Shahrooz";
        Console.WriteLine(person.Name);//Output:Shahrooz
        // Here you are just erasing a copy to reference not the object created.
        // Single memory allocation in case of reference type and  parameter
         // are passed as a copy of reference type .   
        person2 = null;
        Console.WriteLine(person.Name);//Output:Shahrooz

    }
}
public class Person
{
    public string Name { get; set; }
}
Suraj Singh
quelle
@doctorlove hat die Kommentare tatsächlich vergessen :-(, Nun, für den Referenztyp wird nur ein Speicherplatz zugewiesen, und Person 2 oder Person 1 ... sind nur Kopien dieses Referenztyps, die auf den Speicherort zeigen. Löschen einer Kopie des Referenztyps wird nichts beeinflussen.
Suraj Singh
@doctorlove Danke für deinen Vorschlag, das werde ich mir merken.
Suraj Singh
0

Sie kopieren zuerst den Verweis person1auf person2. Jetzt person1und person2beziehen sich auf das gleiche Objekt, die Modifikationen auf den Wert des Objekts bedeutet ( das heißt die Änderung der NameEigenschaft) kann unter beiden Variablen zu beobachten. Wenn Sie dann null zuweisen, entfernen Sie die Referenz, der Sie gerade zugewiesen haben person2. Es ist nur person1jetzt zugeordnet. Beachten Sie, dass die Referenz selbst nicht geändert wird.

Wenn Sie eine Funktion hätten, die ein Argument als Referenz akzeptiert, könnten Sie als reference to person1 Referenz übergeben und die Referenz selbst ändern:

public class Program
{
    private static void Main(string[] args)
    {
        var person1 = new Person { Name = "Test" };
        Console.WriteLine(person1.Name);

        PersonNullifier.NullifyPerson(ref person1);
        Console.WriteLine(person1); //Output: null
    }
}


class PersonNullifier
{
    public static void NullifyPerson( ref Person p ) {
        p = null;
    }
}

class  Person {
    public string Name{get;set;}
}
Asad Saeeduddin
quelle
0

Sprichwort:

person2.Name = "Shahrooz";

folgt der Referenz person2und "mutiert" (ändert) das Objekt, zu dem die Referenz führt. Die Referenz selbst (der Wert von person2) ist unverändert; Wir verweisen immer noch auf dieselbe Instanz unter derselben "Adresse".

Zuweisen zu person2wie in:

person2 = null;

ändert die Referenz . Es wird kein Objekt geändert. In diesem Fall wird der Referenzpfeil von einem Objekt zu "nichts" "verschoben" null. Eine Zuordnung wie person2 = new Person();würde aber auch nur die Referenz ändern. Kein Objekt ist mutiert.

Jeppe Stig Nielsen
quelle