Тип посилання в C #


79

Розглянемо цей код:

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на person2і Nameвластивість person2змінюється, Nameз person1також буде змінено. person1і person2мають однакові посилання.

Чому це , що , коли person2 = null, то person1змінна не буде нульовим або?

Відповіді:


183

Обидва personі person2є посиланнями на один і той же об'єкт. Але це різні посилання. Отже, коли ви біжите

person2 = null;

Ви змінюєте лише посилання person2, залишаючи посиланняperson та відповідний об’єкт без змін.

Я думаю, найкращий спосіб пояснити це спрощеною ілюстрацією. Ось як виглядала ситуація раніше person2 = null :

Перед нульовим призначенням

І ось малюнок після нульового призначення:

Введіть тут опис зображення

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


22
Іншими словами, person і person2 вказує на щось, потім person2 вказує ні на що, встановлюючи значення null.
rro

2
@ShahroozJefri ㇱ В принципі, person1і person22 різні візитні картки , які містять адреси. Якщо ви робите , person1 = person2то person1тепер копія person2. Вони все ще різні візитні картки, але обидві містять одну і ту ж адресу, що вказує на один і той же об’єкт. Сам об’єкт не змінюється.
Нолонар

Я б прибрав стрілку на person2другому малюнку, оскільки там нічого не вказано - це не звисаюче посилання.
Sameer Singh

@SameerSingh, готово, дякую за відповідь. Спочатку я думав про те саме, але чомусь передумав.
Андрій

2
При використанні посилань, створенні змінної та присвоєнні її іншому вказівнику, створюється річ під назвою Псевдонім, де, скажімо, 3 змінних, що вказують на одне і те ж місце пам'яті. Коли ви зводите нанівець одну з цих змінних, є одна змінна, яка менше вказує на це місце в пам'яті, коли всі змінні вказують на NULL, GC очищає це місце, щоб виділити нову змінну, принаймні в Java, GC працює таким чином D:
NemesisDoom

55

Розглянемо person1і person2 як вказівники на деяке місце в сховищі. На першому кроці лише person1зберігається адреса об’єкта із сховища, а пізніше person2- адреса місця розташування об’єкта в пам’яті. Пізніше , коли ви призначаєте nullна person2, person1залишається незмінним. Ось чому ви бачите результат.

Ви можете прочитати: Цінність проти еталонних типів від Джозефа Альбахарі

Однак із типовими посиланнями об'єкт створюється в пам'яті, а потім обробляється через окреме посилання - швидше як вказівник.

Я спробую зобразити те саме поняття, використовуючи наступну схему.

введіть тут опис зображення

Створено новий об'єкт типу person і person1посилання (вказівник) вказує на місце пам'яті в сховищі.

введіть тут опис зображення

Створено нове посилання (покажчик), person2яке вказує на те саме в сховищі.

введіть тут опис зображення

Змінено ім'я властивості об'єкта на нове значення, person2оскільки обидва посилання вказують на один і той же об'єкт, Console.WriteLine(person1.Name);виводить Shahrooz.

введіть тут опис зображення

Після призначення nullв якості person2посилання, він буде вказувати нічого, але person1все ще тримає посилання на об'єкт.

(Нарешті, для управління пам’яттю ви повинні побачити «Стек - це деталь реалізації», частина перша та «Стек - деталь реалізації», частина друга від Еріка Ліпперта)


3
Будь-яке посилання на "купу" є недоречною деталлю реалізації. Я навіть не впевнений, що це правда, цього точно не потрібно.
jmoreno

@jmoreno, ось чому останній абзац, насправді я дотримувався стилю в статті Джозефа Альбахарі, посилання вгорі відповіді
Хабіб

4
@jmoreno, що стосується цієї відповіді, то правильно, що об'єкт типу personбуде зберігатися в купі та його посиланнях person1та person2знаходитись у стеку. Але я погоджуюсь, що це більше деталі реалізації. Але наявність стека та купи у відповіді (як у статті Альбахарі) зробило б більш чітким IMO.
Хабіб

1
@jmoreno та Habin: Я замінив використання купи на сховище , оскільки використання або невикористання * конкретного механізму зберігання, широко відомого як купа, не має значення.
Пітер Геркенс

1
Чудова відповідь, але вона трохи перевершує питання, яке задають.
Бритва

14

Ви змінили person2посилання null, але person1посилання там не здійснюється.

Я маю на увазі, що якщо ми розглянемо person2і person1перед призначенням, тоді обидва посилаються на один і той же об’єкт. Потім ви призначаєте person2 = null, тож особа 2 зараз посилається на інший тип. Він не видалив об’єкт, на який person2посилався.

Я створив цей gif для ілюстрації:

введіть тут опис зображення


13

Тому що ви встановили посилання на null.

Коли ви встановлюєте посилання на null, саме посилання єnull .., а не об’єктом, на який він посилається.

Подумайте про них як про змінну, яка містить зміщення від 0. personмає значення 120. person2має значення 120. Дані зі зміщенням 120 є Personоб’єктом. Коли ви робите це:

person2 = null;

..you're ефективно кажучи person2 = 0;. Однак personвсе ще має значення 120.


тож особа 2 та особа не мають однакових посилань?

Вони посилаються на один і той же об’єкт .. але це окремі посилання. Це зводиться до copy-by-valueсемантики. Ви копіюєте значення посилання.
Саймон Уайтхед

4

І те, personі person2 інше на один і той же об’єкт. Тому при зміні назви будь-якого з них обидва будуть змінені (оскільки вони вказують на одну і ту ж структуру в пам'яті).

Але коли ви встановлюєте person2значення null, ви перетворюєте person2на нульовий покажчик, так що це не вказує на той самий об'єкт, що personвже. Він не робить нічого для самого об'єкта, щоб знищити його, і з тих пірperson все ще вказує / посилається на об'єкт, він також не буде вбитий через збір сміття.

Якщо ви також встановили person = null, і у вас немає інших посилань на об’єкт, він врешті видалить збирач сміття.


2

person1і person2вкажіть на ту саму адресу пам'яті. Коли ви нульові person2, ви нульові посилання, а не адресу пам'яті, тому person1продовжує посилатися на цю адресу пам'яті, це і є причиною. Якщо ви зміните Classs Personна a Struct, поведінка зміниться.


Вони можуть вказувати або не вказувати на одну і ту ж адресу пам'яті (або будь-яку іншу адресу). Важливо те, що вони ідентифікують один і той же об’єкт . У деяких видах одночасного збирача сміття може бути можливим, що під час переміщення об’єкта деякі посилання можуть містити стару адресу, а інші ідентифікують нову [код, який пише на будь-яку адресу, може заблокувати, поки всі посилання не будуть оновлені, і код, який порівнює адреси, повинен був би побачити, чи одна адреса була "поточною", а інша - ні, і якщо так, то знайти "нову" адресу, пов'язану зі старою.]
supercat

2

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

Єдиними операціями, які виконуються безпосередньо над ідентифікаторами об'єкта, є створення нового об'єкта та отримання ідентифікатора, отримання "порожнього" ідентифікатора (тобто нульовий), копіювання ідентифікатора об'єкта в змінну або місце зберігання, яке може його утримувати, перевірка, чи є два ідентифікатори об'єктів збігаються (посилаються на той самий об'єкт). Усі інші запити вимагають від системи знайти об’єкт, на який посилається ідентифікатор, та діяти на нього (не впливаючи на змінну чи іншу сутність, що тримала ідентифікатор).

У існуючих реалізаціях .NET змінні об'єкти, швидше за все, містять вказівники на об'єкти, що зберігаються у купі, що збирається сміттям, але це не корисна деталь реалізації, оскільки існує критична різниця між посиланням на об'єкт та будь-яким іншим покажчиком. Як правило, вважається, що вказівник відображає місце розташування чогось, що залишатиметься досить довгим, щоб працювати з ним. Посилання на об'єкти ні. Шматок коду може завантажити регістр SI з посиланням на об'єкт, розташований за адресою 0x12345678, почати його використовувати, а потім перервати, поки збирач сміття переміщує об'єкт на адресу 0x23456789. Це би звучало як катастрофа, але сміття вивчить метадані, пов’язані з кодом, зауважимо, що код використовував SI для утримання адреси об’єкта, який він використовував (тобто 0x12345678), визначити, що об’єкт, що знаходився в 0x12345678, було переміщено в 0x23456789, та оновити SI, щоб він містив 0x23456789, перш ніж він повернувся. Зверніть увагу, що в цьому сценарії чисельне значення, що зберігається у SI, було змінено збирачем сміття, але воно посилалосяодин і той же об'єкт до переїзду та після. Якщо перед переміщенням воно посилалося на 23592-й об'єкт, створений з моменту запуску програми, він буде продовжувати робити це після цього. Цікаво, що .NET не зберігає жодного унікального та незмінного ідентифікатора для більшості об’єктів; з урахуванням двох знімків пам'яті програми, не завжди можна буде визначити, чи існує якийсь конкретний об'єкт у першому знімку, або якщо всі сліди до нього були залишені та створено новий об'єкт, який, схоже, схожий на всі спостережувані деталі.


1

person1 та person2 - це дві окремі посилання на стек, які вказують на один і той же об’єкт Person у купі.

Коли ви видаляєте одне з посилань, воно видаляється зі стеку і більше не вказує на об’єкт Person у купі. Інше посилання залишається, все ще вказуючи на існуючий об’єкт Person у купі.

Як тільки всі посилання на об’єкт Person будуть видалені, тоді збірник сміття видалить об’єкт із пам'яті.


1

Коли ви створюєте тип посилання, його фактично копіюється посилання з усіма об'єктами, що вказують на одне і те ж розташування пам'яті, однак якщо ви призначили Person2 = Null, це не матиме ефекту, оскільки person2 - це просто копія контрольної особи, і ми щойно видалили копію посилання .


1

Зверніть увагу, що семантику значення можна отримати, змінивши значення на 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; }
}

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

@doctorlove фактично забув коментарі :-(, Ну, для довідкового типу виділено лише одне місце в пам'яті, а Person 2 або Person 1 ... - це просто копія того довідкового типу, які вказують на місце в пам'яті, стирання копії посилального типу нічого не вплине.
Сурадж Сінгх

@doctorlove Дякую за вашу пропозицію, я пам’ятаю це.
Сурадж Сінгх

0

Спочатку скопіюйте посилання 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;}
}

0

Кажучи:

person2.Name = "Shahrooz";

слідує за посиланням person2та "мутує" (змінює) об'єкт, до якого посилання випадково веде. Сама посилання (по вартості зperson2 ) не змінюється; ми все ще посилаємось на той самий екземпляр за тією ж "адресою".

Призначення person2як у:

person2 = null;

змінює посилання . Жоден об'єкт не змінюється. У цьому випадку посилання стрілок «переміщаються» від одного об'єкта до «нічого», null. Але таке призначення person2 = new Person();також також лише змінить посилання. Жоден об’єкт не мутується.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.