3

Albaharis 的“C# 4.0 IN A NUTSHELL”第 4 版书在第 249 页上指出:“......调用 object.ReferenceEquals 保证正常的引用相等性。”

所以,我决定测试一下。

首先我尝试了这样的值类型。

 int aa1 = 5;
 int aa2 = aa1;
MessageBox.Show("object.ReferenceEquals(aa1,aa2) is: " + object.ReferenceEquals(aa1, aa2)); 

正如我所料,结果是假的:object.ReferenceEquals(aa1, aa2) is: False

生活很好。然后我尝试了一个像这样的可变引用类型。

System.Text.StringBuilder sblr1 = new System.Text.StringBuilder();
sblr1.Append("aaa");
System.Text.StringBuilder sblr2 = sblr1;          
MessageBox.Show("object.ReferenceEquals(sblr1,sblr2) is: " + object.ReferenceEquals(sblr1, sblr2)); 

正如我所料,结果是真的 object.ReferenceEquals(sblr1, sblr2) is: True

生活还是不错的。然后我想,既然它是一种可变引用类型,那么如果我将一个变量更改为 null,那么两者都应该为 null。所以我尝试了以下方法。

System.Text.StringBuilder sblr1 = new System.Text.StringBuilder();
sblr1.Append("aaa");
System.Text.StringBuilder sblr2 = sblr1;
sblr1 = null;
MessageBox.Show("object.ReferenceEquals(sblr1,sblr2) is: " + object.ReferenceEquals(sblr1, sblr2)); 

我希望它们都为空。但我得到的结果是 False: object.ReferenceEquals(sblr1, sblr2) is: False

现在生活不是那么好。我认为如果它覆盖了 sblr1 的内存位置,那么它也会覆盖 sblr2 的内存位置。

然后我想也许他们指向两个不同的空值,所以我尝试了这个:

System.Text.StringBuilder sblr1 = new System.Text.StringBuilder();
sblr1.Append("aaa");
System.Text.StringBuilder sblr2 = sblr1;
sblr2 = null;
MessageBox.Show("sblr1 == " + sblr1 + " and sblr2 == " + sblr2);

但在这里,只有一个像这样指向一个空值。sblr1 == aaa 和 sblr2 ==

只有一个是空的。

它显示了我对字符串对象等不可变引用类型所期望的行为。使用字符串对象,我可以执行以下操作:

string aa1 = "aaX";
string aa2 = "aaX";
MessageBox.Show("object.ReferenceEquals(aa1,aa2) is: " + object.ReferenceEquals(aa1, aa2)); 

他们都会引用同样的东西。object.ReferenceEquals(aa1, aa2) 是:True 因为“aaX”只被写入程序集一次。

但如果我这样做:

string aa1 = "aaX";
string aa2 = "aaX"; 
aa1 = null; 
MessageBox.Show("After aa1 is null(" + aa1 + "), then aa2 is: " + aa2);

然后他们指向不同的东西是这样的: aa1 为 null() 之后,然后 aa2 为:aaX

那是因为字符串对象是不可变的。内存位置不会被覆盖。相反,该变量指向堆内存中存在新值的不同位置。在上面的示例中将 aa1 更改为 null 意味着 aa1 将指向堆内存上的不同位置。

那么为什么可变引用类型的行为与不可变引用类型相同呢?


编辑下午 4:03 和 4:08

我最近试过这个:

System.Text.StringBuilder sblr1 = new System.Text.StringBuilder();
sblr1.Append("aaa");

// sblr1 and sblr2 should now both point to the same location on the Heap that has "aaa".
System.Text.StringBuilder sblr2 = sblr1;

System.Text.StringBuilder sblr3 = new System.Text.StringBuilder();
sblr3.Append("bbb");

sblr1 = sblr3;
MessageBox.Show("sblr1 == " + sblr1 + " and sblr2 == " + sblr2 + " and sblr3 == " + sblr3);

这给了我: sblr1 == bbb 和 sblr2 == aaa 和 sblr3 == bbb

这更像是我所期待的结果。多亏了评论,我现在看到,我下意识地期望 null 充当内存位置。

4

3 回答 3

1

我认为如果它覆盖了 sblr1 的内存位置,那么它也会覆盖 sblr2 的内存位置。

这是你的误解。

当你写这个:

System.Text.StringBuilder sblr2 = sblr1;

您将sblr2变量分配为对与StringBuilder指向的实例相同的实例的引用sblr1。这两个变量现在指向同一个引用。

然后你写:

sblr1 = null;

这会将sblr1变量更改为现在为空引用。您根本没有更改内存中的实例。

这与引用是否为可变类型无关。您正在更改变量,而不是它们引用的实例。

至于您的字符串示例:

那是因为字符串对象是不可变的。内存位置不会被覆盖

这实际上是不正确的。您将一个字符串变量设置为这一事实null与字符串是不可变的并没有任何关系。这是一个单独的问题。

那么为什么可变引用类型的行为与不可变引用类型相同呢?

您看到的行为与可变性无关。它是所有引用类型(无论是不可变的还是可变的)的标准行为。可变性是一个不同的问题。

可变性的主要问题是:

假设你有一个类,像这样:

class Foo
{
    public int Bar { get; set; }
}

如果你这样写:

Foo a = new Foo();
a.Bar = 42;
Foo b = a;
b.Bar = 54;
Console.WriteLine(a.Bar); // Will print 54, since you've changed the same mutable object

对于不可变类型,这不会发生,因为您无法更改Bar- 相反,如果您创建一个不可变类:

class Baz
{
    public Baz(int bar) { this.Bar = bar; }

    public int Bar { get; private set; }
}

你需要写:

Baz a = new Baz(42);
Baz b = a;

// This isn't legal now:
// b.Bar = 54;
// So you'd write:
b = new Baz(54); // Creates a new reference

或者,您可以让类在“更改”操作上返回一个新引用,即:

class Baz
{
    public Baz(int bar) { this.Bar = bar; }

    public int Bar { get; private set; }

    public Baz Alter(int newValue) { return new Baz(newValue); } // May copy other data from "this"
}

然后当你写:

Baz a = new Baz(42);
Baz b = a.Alter(54); // b is now a new instance

这就是发生的情况string- 所有方法都返回一个新实例,因为字符串是不可变的,因此您永远不能“更改”现有副本。

于 2013-04-05T19:20:57.737 回答
0

这与可变性无关。此处涉及的规则对于所有引用类型都是相同的。引用类型的非非ref变量out(或成员或集合中的槽)是引用(duh)。这意味着它指的是某个对象。它不引用另一个引用,或者另一个引用所在的位置(例如,一个变量)。当您分配给变量(或成员,或集合中的槽)时,您会更改该位置的引用;您不会覆盖任何对象的任何部分(当然,您分配的成员除外,如果它是成员)。

在您的代码中,有两个变量srbl1srbl2,每个变量都存储对同一字符串构建器对象的引用。分配给任一更改会覆盖其中一个引用(例如,使用null,或使用对不同对象的引用)。

于 2013-04-05T19:23:21.110 回答
0

更改引用只是更改某些内容所指的内容。它不会改变对象本身。

一种看待它的方法是想象一个整数数组:

int[] foo = new int[] {0, 1, 2, 3, 4, 5};

您可以创建两个索引来引用数组中的项目:

int ix = 1;
int iy = ix;

然后foo[ix] == foo[iy]

如果您然后 write foo[ix] = 42, thenfoo[ix] == foo[iy]仍然是正确的,因为您更改了索引引用的值。

但是,如果您更改索引,那么ix = 3, 那么ixiy指的是不同的东西。

引用类型的工作方式完全相同。

当您编写 时sblr1 = new StringBuilder(),您已经创建了一个新的StringBuilder对象实例并sblr1指向它。如果你然后写sblr2 = sblr1,你只是在sblr2指出同一件事。然后sblr1 = null只是说,“sblr1 不再指向任何东西。” 它实际上不会影响它所引用的项目,就像将索引更改为数组不会影响被索引的项目的值一样。

于 2013-04-05T19:28:25.133 回答