引用类型和值类型的场景

4

我一直在尝试深入理解引用类型和值类型。就在我认为我已经理解了它们的时候,我遇到了这种情况...

我创建了一个包含单个对象的类。

class Container
{
    public object A {get; set;}
}

当我创建一个 Container 类的实例(a)时,我正在创建一个引用类型的实例。我将一个整数分配给类中的对象。据我所知,这将作为一个对象进行装箱,成为另一个引用类型。
int start = 1;  
Container a = new Container{ A=start };

我创建了Container类的另一个实例(b),但将第一个容器的值赋给它,现在b的值是对a的引用。

Container b = a;

正如预期的那样,当我打印出a.A和b.A的值时,它们是相同的。

Console.WriteLine("a.A={0},b.A={1}",a.A,b.A);
//a.A=1,b.A=1

正如预期的那样,当我更改a.A的值时,由于它们都引用同一对象,b.A的值也会相应地发生更改。

a.A = 2;

Console.WriteLine("a.A={0},b.A={1}",a.A,b.A);
// a.A=2,b.A=2

现在我决定使用单独的本地对象来尝试这个。再次,我将整数装箱到第一个对象中,并将第一个对象的值分配给第二个对象。我相信此时对象应该是引用类型,所以c和d应该引用同一个对象。不改变任何内容,它们返回相同的值。

int start = 1;
object c = start;
object d = c;

Console.WriteLine("c={0},d={1}",c,d);
// c=1,d=1

与之前一样,当更改初始对象的值时,我希望两个对象的值相同。

c = 2;

Console.WriteLine("c={0},d={1}",c,d);
// c=2,d=1

当我打印这两个对象的结果时,d的值不像以前那样改变。 请问有人能解释一下为什么这种情况下赋值与之前的不同吗? 谢谢。
4个回答

10

这是您的第一个错误:

我创建了另一个容器类的实例(b),但将第一个容器的值赋给它,因此b的值现在是对a的引用。

Container b = a;
那并没有创建另一个实例,它声明了另一个变量。现在两个变量都引用同一个对象。
(我会不断编辑此答案,继续阅读...)
接下来:
int start = 1;
object c = start;
object d = c;
Console.WriteLine("c={0},d={1}",c,d); // c=1,d=1
与之前一样,当更改初始对象的值时,我希望两个对象的值是相同的。
c = 2;
Console.WriteLine("c={0},d={1}",c,d); // c=2,d=1
那不是改变一个“对象”,而是改变一个“变量”。每次赋值都会复制一个值,但其中一次还执行了装箱操作。让我们稍微简化一下:
object c = "first string";
object d = c;

现在没有任何拆箱(boxing)涉及 - 这将使它更容易理解。

两个变量目前都具有指向同一个对象的值。这是由于赋值造成的,但没有其他东西将这两个变量连接起来。它们目前恰好具有相同的值,但它们是独立的变量。现在让我们改变其中一个:

c = "different string";
那已经改变了c的值,使它引用不同的对象。如果您打印出cd的值,它们分别是"不同的字符串"和"第一个字符串"。改变c的值不会改变d的值
现在,让我们回到之前的场景,看看为什么它不同。在那里,您有:
a.A = 2;

那并没有改变a的值,它只是在改变a所引用对象的数据。

让我们使用一个现实世界的类比来更容易理解。假设我们所有的变量都是写有房屋地址的纸条。a.A = 2;的变化就像是在该地址的房屋内部进行改变。当然,任何其他写有相同地址的纸条的人都会看到这个改变。

接下来想象一下你的c/d情景。同样,想象我们有两张纸片,由于赋值操作符,它们都写有相同的地址。现在将新值分配给c变量本身,就像是擦掉了c纸条上的地址并重新写上不同的地址。这并不会改变d纸条,它仍然写着旧地址,如果你去访问那个房子,你会发现没有任何改变。只有纸上的内容发生了改变。

这有帮助吗?


3

区别在于封装对象Container

第一种情况下,你有一个包含引用的对象。当你说你创建了Container类的一个新实例时,你并没有。你只是复制了对现有对象的引用。由于你有两个引用指向同一个对象,所以你可以通过一个引用更改对象的内容,并通过另一个引用读取它。

 a     b          a     b
  \   /            \   /
   \ /              \ /
---------        ---------
|       |        |       |
|   A   |        |   A   |
|   |   |        |   |   |
----|----   ->   ----|----
    |                |
---------        ---------
|       |        |       |
|   1   |        |   2   |
|       |        |       |
---------        ---------

在第二种情况下,您也有两个指向相同对象的引用,但在此情况下,您直接引用了装箱对象,而不是容器。当您将新值分配给其中一个引用时,您将获得两个单独的对象。一个对象是装箱后的1,另一个对象是装箱后的2。当您将新值分配给b时,它不会将该值放入其指向的盒子中,而是创建一个包含新值的新装箱对象。
 a     b             a          b
  \   /              |          |
   \ /               |          |
---------   ->   ---------  ---------
|       |        |       |  |       |
|   1   |        |   1   |  |   2   |
|       |        |       |  |       |
---------        ---------  ---------

2

在第一个示例中,你有这个:

 a      b  
  \    /  
   \  /  
   |  |  
   v  v  
(Container)

这里只有一个容器实例。两个变量都指向这个容器。当你改变容器时,两个变量都能看到这个变化。
然而在这段代码中:
object c = 1;
object d = c;

第二个赋值语句并不意味着"d是c的别名",只是意味着在第二个赋值后,cd指向同一个对象。
c = 2;

现在,您将c重新分配给一个新的装箱整数,因此cd现在指向两个不相关的对象。

 Before                 After

 c      d               c                    d
  \    /         c=2    |                    |
   \  /          ---->  |                    |
   |  |                 |                    |
   v  v                 v                    v
(boxed integer 1)      (boxed integer 2)    (boxed integer 1)

0
在您的第一个场景中,您引用了堆上的一个对象。并且您有两个变量(“a”,“b”)引用此相同的对象。这就是为什么当您更改此对象的成员时,您会看到它在两个变量上都得到反映的原因。
在您的第二种情况下,您正在做完全不同的事情。当您执行以下操作时:
c = 2

实际上,您创建了一个全新的对象。 int 的值类型被转换为对象,因此创建了一个新的引用对象。此时,您的“d”变量不再引用与您的“c”变量相同的对象。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接