Тип ссылки в С#
Рассмотрим этот код:
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();
также изменило бы только ссылку. Объект не мутирован.