80

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

Obviously, when assigning person1 to person2 and the Name property of person2 is changed, the Name of person1 will also be changed. person1 and person2 have the same reference.

Why is it that when person2 = null, the person1 variable will not be null either?

4

13 回答 13

184

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

person2 = null;

您只更改了 reference person2,而 referenceperson和相应的对象保持不变。

我想解释这一点的最好方法是用一个简化的插图。这是以前 person2 = null的情况:

在空赋值之前

这是空赋值后的图片:

在此处输入图像描述

如您所见,在第二张图片上person2没有引用任何内容(或者null,严格来说,由于没有引用和引用null是不同的条件,请参阅Rune FS的评论),同时person仍然引用现有对象。

于 2013-08-14T10:38:48.080 回答
55

考虑person1person2 作为指向存储中某个位置的指针。在第一步中,仅从person1存储中获取对象的地址,然后从存储中获取对象person2的内存位置地址。稍后当您分配null给 时person2person1不受影响。这就是你看到结果的原因。

您可能会阅读:Joseph Albahari 的值与引用类型

然而,对于引用类型,对象是在内存中创建的,然后通过单独的引用进行处理——就像指针一样。

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

在此处输入图像描述

创建了一个 person 类型的新对象,并且person1引用(指针)指向存储中的内存位置。

在此处输入图像描述

创建了一个新的引用(指针)person2,它指向存储中的相同。

在此处输入图像描述

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

在此处输入图像描述

分配nullperson2引用后,它将不指向任何内容,但person1仍持有对该对象的引用。

(最后,对于内存管理,您应该看到Eric Lippert的 The Stack Is An Implementation Detail, Part OneThe Stack Is An Implementation Detail, Part 2 )

于 2013-08-14T13:50:03.293 回答
14

您已更改person2为 reference null,但person1没​​有在那里引用。

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

我创建了这个 gif 来说明它:

在此处输入图像描述

于 2013-08-14T10:38:40.967 回答
13

因为您已将引用设置为null.

当您设置对 的引用时null,引用本身是null.. 而不是它所引用的对象。

将它们视为一个变量,它保存从 0person开始的偏移量。具有值 120。person2具有值 120。偏移量 120 处的数据是Person对象。当你这样做时:

person2 = null;

..你实际上是在说,person2 = 0;。但是,person仍然有值 120。

于 2013-08-14T10:39:09.433 回答
4

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

但是当你设置person2为时null,你会person2变成一个空指针,所以它不再指向同一个对象person。它不会对对象本身做任何事情来销毁它,并且由于person仍然指向/引用对象,它也不会被垃圾收集杀死。

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

于 2013-08-14T10:40:40.660 回答
2

person1person2指向相同的内存地址。当您为 null 时person2,您将引用而不是内存地址设为 null,因此person1继续引用该内存地址,这就是原因。如果将 更改Classs Person为 a Struct,则行为将发生变化。

于 2013-08-14T14:40:37.783 回答
2

我发现将引用类型视为保存对象 ID 最有帮助。如果有一个类类型的变量Car,该语句myCar = new Car();要求系统创建一辆新车并报告它的 ID(假设它是对象 #57);然后将“object #57”放入变量myCar中。如果写入Car2 = myCar;,则将“object #57”写入变量 Car2。如果写入car2.Color = blue;,则指示系统找到由 Car2 标识的汽车(例如对象#57)并将其涂成蓝色。

直接在对象 ID 上执行的唯一操作是创建新对象并获取 ID,获取“空白”ID(即 null),将对象 ID 复制到可以保存它的变量或存储位置,检查是否有两个对象 ID 匹配(指同一对象)。所有其他请求都要求系统找到 ID 引用的对象并对该对象采取行动(不影响持有该 ID 的变量或其他实体)。

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

于 2013-08-14T16:40:09.567 回答
1

请注意,您可以通过更改为 a 来获得值语义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; }
}
于 2013-08-14T12:33:26.207 回答
1

当您创建一个引用类型时,它实际上复制了一个所有对象都指向同一内存位置的引用,但是如果您分配了 Person2=Null 它将不起作用,因为 person2 只是引用 person 的副本,我们刚刚删除了一个副本参考

于 2013-08-14T10:54:36.990 回答
1

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

当您删除其中一个引用时,它会从堆栈中删除并且不再指向堆上的 Person 对象。另一个引用仍然存在,仍然指向堆上现有的 Person 对象。

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

于 2013-08-14T10:49:09.803 回答
0

说:

person2.Name = "Shahrooz";

遵循引用person2并“改变”(更改)引用碰巧导致的对象。引用本身(的值person2不变;我们仍然在同一个“地址”引用同一个实例。

分配给person2如下:

person2 = null;

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

于 2013-08-15T06:15:52.327 回答
0

您首先将引用复制person1person2。现在person1并引用同一个对象,这意味着可以在两个变量下观察到person2对该对象值的修改(即更改属性)。Name然后,在分配 null 时,您将删除刚刚分配给的引用person2。它只分配给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;}
}
于 2013-08-14T15:39:02.210 回答
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; }
}
于 2013-08-14T10:40:55.003 回答