Сценарий типов ссылок и значений

Я играл, пытаясь полностью понять типы ссылок и значений. Как раз когда я думал, что у меня это есть, я наткнулся на этот сценарий...

Я создал класс, который будет содержать один объект.

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 не меняется, как раньше.

Может кто-нибудь объяснить, почему задание в этом сценарии отличается от предыдущего?

Спасибо


person fletcher    schedule 17.07.2010    source источник


Ответы (4)


Вот ваша первая ошибка:

Я создаю еще один экземпляр класса Container (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;

Теперь здесь нет никакого бокса — это просто упростит понимание.

Обе переменные в настоящее время имеют значения, относящиеся к одному и тому же объекту. Это связано с присваиванием, но ничто другое не связывает две переменные. В данный момент они имеют одно и то же значение, но это независимые переменные. Теперь давайте изменим один:

c = "different string";

Это изменило значение c для ссылки на другой объект. Если вы распечатаете значения c и d, они будут "другой строкой" и "первой строкой" соответственно. Изменение значения c не меняет значения d.


Теперь давайте вернемся к вашему предыдущему сценарию, чтобы понять, почему он отличается. Там у вас было:

a.A = 2;

Это совсем не меняет значение a. Это изменение данных в объекте, на который ссылается a.

Давайте используем аналогию с реальным миром, чтобы сделать это проще. Предположим, что все наши переменные — это листы бумаги с написанными на них адресами домов. Изменение a.A = 2; похоже на изменение содержимого дома по этому адресу. Конечно, любой другой человек с тем же адресом, который указан на их листе бумаги, увидит изменение.

Теперь подумайте о своем сценарии c/d. Опять же, представьте, что у нас есть два листа бумаги, и из-за оператора присваивания на них написан один и тот же адрес. Теперь ваше присвоение нового значения самой переменной c похоже на стирание адреса на листе бумаги c и запись на нем другого адреса. Это совсем не меняет d листка бумаги — на нем по-прежнему указан старый адрес, и если вы посетите этот дом, вы увидите, что ничего не изменилось. Изменилось только то, что написано на листе бумаги.

Это помогает?

person Jon Skeet    schedule 17.07.2010

Разница заключается в инкапсулирующем объекте Container.

В первом случае у вас есть объект, содержащий ссылку. Когда вы сказали, что создали новый экземпляр класса Container, вы этого не сделали. Вы просто скопировали ссылку на существующий объект. Поскольку у вас есть две ссылки на один и тот же объект, вы можете изменить содержимое объекта по одной ссылке и прочитать его по другой.

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

Во втором случае у вас также есть две ссылки на один и тот же объект, но в этом случае вы напрямую ссылаетесь на упакованный объект, а не на контейнер. Когда вы присваиваете новое значение одной из ссылок, вы получаете два отдельных объекта. Один объект в рамке 1, а другой объект в рамке 2. Когда вы присваиваете новое значение b, он не помещает значение в поле, на которое он указывает, он создает новый объект в рамке, содержащий новое значение. .

 a     b             a          b
  \   /              |          |
   \ /               |          |
---------   ->   ---------  ---------
|       |        |       |  |       |
|   1   |        |   1   |  |   2   |
|       |        |       |  |       |
---------        ---------  ---------
person Guffa    schedule 17.07.2010

В первом примере у вас есть это:

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

Здесь только один экземпляр контейнера. Обе переменные имеют ссылку на этот контейнер. Когда вы изменяете контейнер, обе переменные видят изменения.

Однако в этом коде:

object c = 1;
object d = c;

Второе присваивание не означает, что "d является псевдонимом для c", оно просто означает, что после второго присваивания c и d указывают на один и тот же объект.

c = 2;

Теперь вы переназначаете c новому целому числу в коробке, так что c и d теперь указывают на два разных объекта. Эти два объекта никак не связаны.

 Before                 After

 c      d               c                    d
  \    /         c=2    |                    |
   \  /          ---->  |                    |
   |  |                 |                    |
   v  v                 v                    v
(boxed integer 1)      (boxed integer 2)    (boxed integer 1)
person Mark Byers    schedule 17.07.2010

В вашем первом сценарии у вас была ссылка на один объект в куче. И у вас было две переменные ("a", "b"), относящиеся к одному и тому же объекту. Вот почему, когда вы меняете член этого объекта, вы видите, что это отражается на обеих переменных.

Во втором случае вы делаете что-то совершенно другое. Когда вы сделали:

c = 2

Вы фактически создали совершенно новый объект. Тип значения int был преобразован в объект, поэтому был создан новый ссылочный объект. На данный момент ваша переменная "d" больше не относится к тому же объекту, что и ваша переменная "c".

person Matthew Manela    schedule 17.07.2010