C#中的引用类型

时间:2013-08-14 10:37:06

标签: c# .net pointers

考虑以下代码:

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并更改Name的{​​{1}}属性时,person2的{​​{1}}也会更改改变。 Nameperson1具有相同的参考。

为什么在person1时,person2变量也不会为空呢?

13 个答案:

答案 0 :(得分:183)

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

person2 = null;

您只更改了引用person2,保留了引用person和相应的对象。

我想解释这个的最好方法是简化说明。以下是 person2 = null之前的情况:

Before null assignment

以下是空分配后的图片

Enter image description here

正如您所看到的,在第二张图片person2上没有引用任何内容(或null,严格来说,因为没有引用任何引用null是不同的条件,请参阅{{{ 3}}),而person仍引用现有对象。

答案 1 :(得分:55)

person1person2 视为指针到存储中的某个位置。在第一步中,只有person1从存储中保存对象的地址,后来person2保存了存储中对象的内存位置地址。稍后当您将null分配给person2时,person1不会受到影响。这就是你看到结果的原因。

您可以阅读:Value vs Reference Types from Joseph Albahari

  

但是,对于引用类型,会在内存中创建一个对象   然后通过单独的引用处理 - 而不是像指针一样。

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

enter image description here

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

enter image description here

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

enter image description here

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

enter image description here

null分配给person2引用后,它将指向任何内容,但person1仍然保留对该引用的引用。

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

答案 2 :(得分:14)

您已将person2更改为引用null,但person1并未参考此处。

我的意思是,如果我们在分配之前查看person2person1,则两者都引用相同的对象。然后指定person2 = null,因此人2现在引用不同的类型。它没有删除引用person2的对象。

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

enter image description here

答案 3 :(得分:13)

因为您已将引用设置为null

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

将它们视为保持偏移量为0的变量。person的值为120. person2的值为120.偏移量为120的数据为Person个对象。当你这样做时:

person2 = null;

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

答案 4 :(得分:4)

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

但是当您将person2设置为null时,您将person2设置为空指针,这样就不会指向指向与{相同的对象{1}}了。它不会对对象本身做任何破坏它的事情,因为person仍然指向/引用它不会被垃圾收集杀死的对象。

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

答案 5 :(得分:2)

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

答案 6 :(得分:2)

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

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

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

答案 7 :(得分:1)

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

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

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

答案 8 :(得分:1)

当你创建一个引用类型时,它实际上复制了一个引用,所有对象都指向同一个内存位置,但是如果你已经分配了Person2 = Null它将没有效果,因为person2只是一个引用人的副本,我们只是删除了参考文件

答案 9 :(得分:1)

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

答案 10 :(得分: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; }
}

答案 11 :(得分:0)

您首先将对person1的引用复制到person2。现在person1person2引用同一个对象,这意味着可以在两个变量下观察对该对象的值的修改(即更改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;}
}

答案 12 :(得分:0)

话说:

person2.Name = "Shahrooz";

遵循person2的引用并且“改变”(更改)引用恰好导致的对象。引用本身(person2)保持不变;我们仍然在相同的“地址”引用相同的实例。

分配给person2,如下所示:

person2 = null;

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