Shallow Copy - тип ссылки аномальной природы - PullRequest
3 голосов
/ 08 апреля 2009

Я не могу понять вывод двух наборов фрагментов кода, приведенных ниже. Как на самом деле не получить понятие мелкой копии. Чем это можно объяснить?

Класс:

 public class Person : ICloneable
 {
    public string Name;        
    public int[] arr; 
    public object Clone()
    {
        return this.MemberwiseClone();
    }
 }

Фрагмент кода 1:

 static void Main(string[] args)
    {
        Person p1 = new Person();
        p1.Name = "Name1";
        p1.arr = new int[5] {1,2,3,4,5 };
        Person p2 = (Person)p1.Clone();

        p1.Name = "Name2";
        p1.arr[0] = 11;

        Console.WriteLine(p2.Name);
        Console.WriteLine(p2.arr[0].ToString());
        Console.Read();

    }

Выход: Name1 11

Сомнение: не является ли строка ссылочным типом. Тогда почему p2.Name печатается как «Name1» во фрагменте 1

Фрагмент кода 2:

static void Main(string[] args)
    {
        Person p1 = new Person();
        p1.Name = "Name1";
        p1.arr = new int[5] { 1, 2, 3, 4, 5 };
        Person p2 = (Person)p1.Clone();

        p1.Name = "Name2";
        p1.arr = new int[5] { 11, 12, 13, 14, 15 };

        Console.WriteLine(p2.Name);
        Console.WriteLine(p2.arr[0].ToString());
        Console.Read();

    }

Выход: Name1 1

Ответы [ 4 ]

4 голосов
/ 08 апреля 2009

Массив int [] в вашем примере является ссылочным типом. Это означает, что и p1.arr, и p2.arr указывают на один и тот же массив в памяти.

Если вы измените значение первого индекса на p1.arr, это означает, что значение первого индекса на p2.arr также изменится. Следовательно, поведение в фрагменте кода 1.

Разница во втором фрагменте кода заключается в том, что вы изменяете ссылку на массив p1. Теперь p1.arr является ссылкой на новый объект. p2.arr по-прежнему содержит ссылку на «оригинальный» массив. Таким образом, печать p2.arr[0] печатает 1.

EDIT

Чтобы, надеюсь, убрать некоторые сомнения, возможно, будет понятнее, если вы помните, что набрали:

p1.Name = "Name2";

на самом деле:

p1.Name = new String("Name2");

Это точно так же, как с вашим массивом int []. Вы не изменяете значение p1.Name, вы создаете новый строковый объект и изменяете ссылку для p1.Name на этот новый строковый объект. p2.Name по-прежнему содержит собственную ссылку на «оригинальный» строковый объект, а именно «Name1». Изменяя ссылку p1.Name, , ссылка не изменяется.

2 голосов
/ 08 апреля 2009

Чтобы смягчить ваши сомнения в исходном вопросе.

Строка действительно является ссылочным типом. Следует помнить, что то, что вы делаете с массивом, и то, что вы делаете со строкой, не совпадают.

p1.Name = "Name2"; // new String -equivalent to p1.Name = new string("Name2")
p1.arr[0] = 11; //updated array element

Для массива вы изменяете данные в части памяти, на которую ссылаетесь. Для String вы создаете новую строку (в новой ячейке памяти) и делаете ссылку p1.Name, указывающую на эту вновь выделенную память. p2.Name (который является другой ссылкой) продолжает указывать на исходную область памяти, где хранятся символы «Name1»

Кроме того, из-за неизменности 1009 * строки нет никакого способа изменить p1.Name в p2.Name. Любая попытка изменить строку, например string.replace, создаст новую строку в памяти.

надеюсь, что это поможет.

2 голосов
/ 08 апреля 2009

С http://msdn.microsoft.com/en-us/library/system.object.memberwiseclone.aspx

Метод MemberwiseClone создает поверхностную копию, создавая новый объект, а затем копируя нестатические поля текущего объекта в новый объект. Если поле является типом значения, выполняется побитовая копия поля. Если поле является ссылочным типом, ссылка копируется, а ссылочный объект - нет; следовательно, исходный объект и его клон ссылаются на один и тот же объект.

0 голосов
/ 08 апреля 2009

Просьба посмотреть встроенные комментарии:

static void Main(string[] args)
{
    Person p1 = new Person();
    p1.Name = "Name1";
    p1.arr = new int[5] {1,2,3,4,5 };
    Person p2 = (Person)p1.Clone();

    p1.Name = "Name2"; //Now p1.Name points to a new memory location
    //But p2.Name is still pointing to the location p1.Name had
    // originally pointed to.

    p1.arr[0] = 11; //here p1.arr and p2.arr are pointing to the same place
    //So since you are changing the value of one location it gets 
    //reflected in both

    Console.WriteLine(p2.Name); //Prints Name1
    Console.WriteLine(p2.arr[0].ToString()); //Prints 11
    Console.Read();

}

Во втором фрагменте, когда вы говорите

p1.arr = new int[5] { 11, 12, 13, 14, 15 };

p1.arr указывает на совершенно новое местоположение. (например, что происходит, когда вы делаете p1.Name = "Name2"). Таким образом, это не отражается на p2.arr, который все еще указывает на то же место, на которое ранее указывал p1.arr. (т.е. в массив {1,2,3,4,5})

...