Тип ссылки в С#

Рассмотрим этот код:

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 to person2 и свойства Name person2 изменяется значение Name of person1. person1 и person2 имеют одинаковую ссылку.

Почему это, когда person2 = null, переменная person1 не будет равно null?

Ответ 1

Оба person и person2 являются ссылками на один и тот же объект. Но это разные ссылки. Поэтому, когда вы запускаете

person2 = null;

вы меняете только ссылку person2, оставляя ссылку person и соответствующий объект неизменным.

Я думаю, лучший способ объяснить это с помощью упрощенной иллюстрации. Вот как выглядела ситуация до person2 = null:

Before null assignment

И вот изображение после нулевого присвоения:

Enter image description here

Как вы можете видеть, на втором рисунке person2 ничего не ссылается (или null, строго говоря, поскольку ссылка ничего и ссылка на null не являются другими условиями, см. комментарий Rune FS), а person все еще ссылается на существующий объект.

Ответ 2

Рассмотрим person1 и person2 как указатели для некоторого местоположения в хранилище. На первом этапе только person1 удерживает адрес объекта из хранилища, а затем person2 удерживает адрес ячейки памяти объекта из хранилища. Позже, когда вы назначаете null на person2, person1 остается незатронутым. Вот почему вы видите результат.

Вы можете читать: Значение vs Reference Types от 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 все еще удерживает ссылку на объект.

(Наконец, для управления памятью вы должны увидеть Стек - это часть реализации, часть первая и Стек представляет собой деталь реализации, часть вторая от Эрика Липперта)

Ответ 3

Вы изменили person2 на ссылку null, но person1 там не ссылается.

Я имею в виду, что если мы посмотрим на person2 и person1 до назначения, то оба ссылаются на один и тот же объект. Затем вы назначаете person2 = null, поэтому человек 2 теперь ссылается на другой тип. Он не удалял объект, на который ссылался person2.

Я создал этот gif, чтобы проиллюстрировать его:

enter image description here

Ответ 4

Потому что вы установили ссылку на null.

Когда вы устанавливаете ссылку на null, сама ссылка null.. не объект, который он ссылается.

Подумайте о них как переменной, которая содержит смещение от 0. person имеет значение 120. person2 имеет значение 120. Данные со смещением 120 являются объектами person. Когда вы это сделаете:

person2 = null;

.. вы эффективно говорите: person2 = 0;. Однако person все еще имеет значение 120.

Ответ 5

Оба person и person2 точка для одного и того же объекта. Поэтому, когда вы меняете имя одного из них, оба будут изменены (поскольку они указывают на одну и ту же структуру в памяти).

Но когда вы устанавливаете person2 в null, вы делаете person2 в нулевой указатель, так что это больше не точка для того же объекта, что и person. Он не сделает ничего с самим объектом, чтобы уничтожить его, и поскольку person все еще указывает/ссылается на объект, который он также не будет убит сборкой мусора.

Если вы также установили person = null, и у вас нет других ссылок на объект, он в конечном итоге будет удален сборщиком мусора.

Ответ 6

person1 и person2 указывают на тот же адрес памяти. Когда вы null person2, вы обнуляете ссылку, а не адрес памяти, поэтому person1 продолжает ссылаться на этот адрес памяти, что причина. Если вы измените Classs Person на Struct, поведение изменится.

Ответ 7

Я считаю наиболее полезным думать о ссылочных типах как об объектных идентификаторах. Если у переменной есть переменная типа Car, оператор myCar = new Car(); просит систему создать новый автомобиль и сообщить свой идентификатор (скажем, объект # 57); он затем помещает "объект № 57" в переменную myCar. Если вы пишете Car2 = myCar;, который записывает "объект № 57" в переменную Car2. Если вы пишете car2.Color = blue;, это указывает системе найти автомобиль, идентифицированный Car2 (например, объект № 57) и нарисовать его синим.

Единственными операциями, которые выполняются непосредственно с идентификатором объекта, являются создание нового объекта и получение идентификатора, получение "пустого" идентификатора (то есть null), копирование идентификатора объекта в переменную или папку хранения, которые могут ее удерживать, проверяя соответствие двух идентификаторов объекта (обратитесь к одному объекту). Все остальные запросы просят систему найти объект, на который ссылается идентификатор, и воздействовать на этот объект (не затрагивая переменную или другую сущность, содержащую идентификатор).

В существующих реализациях .NET объектные переменные, вероятно, будут содержать указатели на объекты, хранящиеся в собранной мусором, но это бесполезная деталь реализации, потому что существует критическая разница между ссылкой на объект и любым другим указателем. Обычно предполагается, что указатель представляет собой местоположение того, что останется надолго, чтобы работать с ним. Ссылки на объекты нет. Кусок кода может загрузить регистр SI с ссылкой на объект, расположенный по адресу 0x12345678, начать его использовать, а затем прерваться, пока сборщик мусора перемещает объект по адресу 0x23456789. Это звучит как катастрофа, но мусор будет проверять метаданные, связанные с кодом, обратите внимание, что код использовал SI для хранения адреса используемого объекта (т.е. 0x12345678), определить, что объект, который был в 0x12345678, был перемещен до 0x23456789 и обновить SI до 0x23456789, прежде чем он вернется. Обратите внимание, что в этом сценарии численное значение, сохраненное в SI, было изменено сборщиком мусора, но оно ссылалось на тот же объект перед перемещением и после этого. Если перед перемещением он ссылался на 23 592-й объект, созданный с момента запуска программы, он будет продолжать делать это позже. Интересно, что .NET не хранит уникальный и неизменный идентификатор для большинства объектов; учитывая два моментальных снимка программной памяти, не всегда будет возможно определить, существует ли какой-либо конкретный объект в первом снимке во втором, или если все его следы были оставлены, и создается новый объект, который, похоже, выглядит так, как в все наблюдаемые детали.

Ответ 8

person1 и person2 - две отдельные ссылки на стек, которые указывают на один и тот же объект Person в куче.

Когда вы удаляете одну из ссылок, она удаляется из стека и больше не указывает на объект Person в куче. Другая ссылка остается, все еще указывая на существующий объект Person в куче.

Как только все ссылки на объект Person будут удалены, в конечном итоге Garbage Collector удалит объект из памяти.

Ответ 9

Когда вы создаете ссылочный тип, он фактически копирует ссылку со всеми объектами, указывающими на одну и ту же ячейку памяти. Однако, если вы назначили Person2 = Null, это не будет иметь никакого эффекта, так как person2 - это просто копия ссылочного лица, и у нас есть только стерта копия ссылки.

Ответ 10

Обратите внимание, что вы можете получить семантику значения, изменив ее на 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; }
}

Ответ 11

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; }
}

Ответ 12

Сначала скопируйте ссылку на person1 на person2. Теперь person1 и person2 относятся к одному и тому же объекту, что означает изменение значения этого объекта (т.е. Изменение свойства 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;}
}

Ответ 13

Высказывание:

person2.Name = "Shahrooz";

следует ссылка person2 и "мутирует" (изменяет) объект, к которому приводит ссылка. Сама ссылка (значение person2) не изменяется; мы по-прежнему ссылаемся на один и тот же экземпляр на том же "адресе".

Назначение person2 как в:

person2 = null;

изменяет ссылку. Объект не изменяется. В этом случае эталонная стрелка "перемещается" с одного объекта на "ничего", null. Но присваивание типа person2 = new Person(); также изменило бы только ссылку. Объект не мутирован.