C#中的引用types

考虑这个代码:

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; } } 

很明显,当将person1分配给person2并更改person2Name属性时, person2Name也将被更改。 person1person2有相同的参考。

为什么当person2 = nullperson1variables也不会为空?

personperson2都是同一个对象的引用 。 但是这些是不同的参考。 所以当你跑步的时候

 person2 = null; 

你只改变参考person person2 ,不改变参考person2相应的对象。

我想解释这个最好的方法是一个简单的例子。 person2 = null 之前情况如何:

在空分配之前

这是空分配的图片:

在这里输入图像说明

正如你所看到的,在第二张图片上, person2没有引用任何东西(严格地说,由于引用没有和引用null是不同的条件,请参阅符文FS的注释),而person仍然引用现有的对象。

person1person2 作为指向存储中某个位置的指针 。 在第一步中,只有person1存储对象的地址,后面的person2存储对象的内存地址。 稍后,当您为person2指定null时, person1保持不受影响。 这就是为什么你看到结果。

您可以阅读: 来自Joseph Albahari的Value vs Reference Types

但是,对于引用types,在内存中创build一个对象,然后通过一个单独的引用来处理,而不是像指针一样。

我将尝试使用下面的图来描述相同的概念。

在这里输入图像描述

创build一个新types的person对象, person1引用(指针)指向存储器中的内存位置。

在这里输入图像描述

创build一个新的参考(指针) person2指向相同的存储。

在这里输入图像描述

由于两个引用都指向同一个对象Console.WriteLine(person1.Name);将对象属性Name更改为新值Console.WriteLine(person1.Name); 输出Shahrooz

在这里输入图像描述

在将person2引用赋值为null之后,它将指向任何内容,但是person1仍然保存对该对象的引用。

(最后对于内存pipe理,你应该看到堆栈是一个实现细节,第一部分 , 堆栈是一个实现细节, Eric Lippert的第二部分 )

您已将person2更改为引用null ,但是person1不在此处引用。

我的意思是,如果我们在赋值之前看person2person1 ,那么两个引用同一个对象。 然后,你分配person2 = null ,所以人2现在引用一个不同的types。 它没有删除person2被引用的对象。

我已经创build了这个gif来说明:

在这里输入图像描述

因为您已将参考设置为null

当您将引用设置为null ,引用本身是null ,而不是它引用的对象。

把它们看作一个variables,保存从0开始的偏移量。 person的值是120. person2的值是120.偏移量120的数据是Person对象。 当你这样做:

 person2 = null; 

..你有效地说, person2 = 0; 。 但是, person的价值仍然是120。

person person2 指向同一个对象。 因此,当你改变任何一个的名字时,两者都会被改变(因为它们指向内存中的相同结构)。

但是当你把person2设置为null ,你把person2变成了一个空指针,这样就不会再指向同一个对象了。 它不会对对象本身做任何事情来摧毁它,因为person仍然指向/引用对象,它不会被垃圾收集杀死。

如果您还设置person = null ,并且没有其他对象的引用,则最终将被垃圾收集器删除。

person1person2指向相同的内存地址。 当你为空的人person2 ,你空引用,而不是内存地址,所以person1继续重新回到这个内存地址,这就是原因。 如果将Classs Person更改为Struct ,则行为将会改变。

我觉得最有用的是将引用types视为持有对象ID。 如果有一个Car类的variables,语句myCar = new Car(); 要求系统创build一辆新车并报告其ID(假设它是对象#57); 然后将“object#57”放入variablesmyCar 。 如果有人写Car2 = myCar; ,将“object#57”写入variablesCar2。 如果有人写car2.Color = blue; ,指示系统find由Car2识别的汽车(例如对象#57)并将其涂成蓝色。

唯一直接在对象ID上执行的操作是创build一个新对象并获取ID,得到一个“空白”ID(即空),将对象ID复制到一个variables或存储位置,对象ID匹配(引用同一个对象)。 所有其他请求要求系统查找由ID引用的对象,并对该对象执行操作(不影响variables或持有该ID的其他实体)。

在.NET的现有实现中,对象variables很可能保存指向存储在垃圾回收堆上的对象的指针,但是这是一个无益的实现细节,因为对象引用和其他types的指针之间存在严重差异。 通常假设一个指针代表的东西的位置将保持足够长的时间来处理。 对象引用不。 一段代码可以通过引用位于地址0x12345678的对象加载SI寄存器,开始使用它,然后在垃圾收集器将对象移动到地址0x23456789时被中断。 这听起来像是一场灾难,但垃圾将检查与代码相关的元数据,观察代码使用SI来保存它正在使用的对象的地址(即0x12345678),确定已经移动到0x12345678的对象到0x23456789,并在返回之前更新SI来保存0x23456789。 请注意,在这种情况下,存储在SI中的数值已被垃圾收集器更改,但是在移动之前和之后引用同一个对象 。 如果在移动之前引用了自程序启动以来创build的第23,592个对象,则将在之后继续执行。 有趣的是,.NET不会为大多数对象存储任何唯一且不可变的标识符。 给定一个程序内存的两个快照,并不总是能够判断第一个快照中是否存在任何特定的对象,或者是否所有的对象都被抛弃了,并创build了一个新的对象,看起来像是在所有可观察的细节。

person1和person2是堆栈上的两个单独的引用,指向堆上的同一个Person对象。

删除其中一个引用时,将从堆栈中删除,而不再指向堆上的Person对象。 另一个引用仍然存在,仍然指向堆上现有的Person对象。

一旦对Person对象的所有引用都被删除,垃圾收集器最终将从内存中删除该对象。

当你创build一个引用types的时候,它实际上是复制了一个指向同一个内存位置的所有对象的引用,但是如果你指定了Person2 = Null,它将不起作用,因为person2只是引用人的一个副本,而我们刚刚删除了一个副本的参考

请注意,您可以通过更改为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; } } 
 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; } } 

您首先person1 person2的引用复制到person2 。 现在, person1person2引用同一个对象,这意味着可以在这两个variables下观察到该对象的值的修改(即更改Name属性)。 然后,在分配空值时,您将删除刚分配给person2的引用。 现在只分配给person1 。 请注意,引用本身并没有改变。

如果你有一个通过引用接受参数的函数,你可以通过reference to person1传递reference to person1 的引用 ,并且能够改变引用本身:

 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;} } 

他说:

 person2.Name = "Shahrooz"; 

跟随着person2的引用,并且“改变”(改变)引用发生的对象。 参考本身( person2 )不变; 我们仍然在相同的“地址”处引用相同的实例。

如下所示分配给person2

 person2 = null; 

改变参考 。 没有对象被改变。 在这种情况下,参考箭头从一个对象“移动”到“无”,为null 。 但是像person2 = new Person(); 也只会改变参考。 没有对象被突变。