Объяснение ссылочного типа и значимого типа
можно представить что обьект (Person p) это лист блокнота на котором написан номер комнаты, в которой лежит ваше добро - name и age (допустим яблоко и банка сока).Когда вы передаете обьект класса по значению, то вы берете еще один лист на него копируете то что написано на исходном (номер комнаты) и передаете методу. Метод будет знать в какой комнате лежит ваше добро и конечно может подменить яблоко на грушу и сок выпить (может даже сжечь всю комнату :) ), но на начальной листе он ничего написать не сможет (он ведь у вас остался :) ).
А когда передаете обьект по ссылке (с помощью ключевого слова ref) то тут вы методу передаете непосредственно свой лист. В этом случаем метод не только получает доступ к вашему добру, но может переписать и номер комнаты.
И в таком случае не надо удивляться если в следующий раз, когда заходите сьесть свое яблоко, по записанному на листе номеру придете в сортир :)))))
Все эти типы данных можно разделить на типы значений, еще называемые значимыми типами, (value types) и ссылочные типы (reference types). Важно понимать между ними различия.
Типы значений:
Целочисленные типы (
byte, sbyte, short, ushort, int, uint, long, ulong)Типы с плавающей запятой (
float, double)Тип
decimalТип
boolТип
charПеречисления
enumСтруктуры (
struct)
Ссылочные типы:
Тип
objectТип
stringКлассы (
class)Интерфейсы (
interface)Делегаты (
delegate)
В чем же между ними различия? Для этого надо понять организацию памяти в .NET. Здесь память делится на два типа: стек и куча (heap). Параметры и переменные метода, которые представляют типы значений, размещают свое значение в стеке. Стек представляет собой структуру данных, которая растет снизу вверх: каждый новый добавляемый элемент помещается поверх предыдущего. Время жизни переменных таких типов ограничено их контекстом. Физически стек - это некоторая область памяти в адресном пространстве.
Когда программа только запускается на выполнение, в конце блока памяти, зарезервированного для стека устанавливается указатель стека. При помещении данных в стек указатель переустанавливается таким образом, что снова указывает на новое свободное место. При вызове каждого отдельного метода в стеке будет выделяться область памяти или фрейм стека, где будут храниться значения его параметров и переменных.
Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Program{ static void Main(string[] args) { Calculate(5); } static void Calculate(int t) { int x = 6; int y = 7; int z = y + t; }} |
Пи запуске такой программы в стеке будут определяться два фрейма - для метода Main (так как он вызывается при запуске программы) и для метода Calculate:

При вызове этого метода Calculate в его фрейм в стеке будут помещаться значения t, x, y и z. Они определяются в контексте данного метода. Когда метод отработает, область памяти, которая выделялась под стек, впоследствии может быть использована другими методами.
Причем если параметр или переменная метода представляет тип значений, то в стеке будет храниться непосредсвенное значение этого параметра или переменной. Например, в данном случае переменные и параметр метода Calculate представляют значимый тип - тип int, поэтому в стеке будут храниться их числовые значения.
Ссылочные типы хранятся в куче или хипе, которую можно представить как неупорядоченный набор разнородных объектов. Физически это остальная часть памяти, которая доступна процессу.
При создании объекта ссылочного типа в стеке помещается ссылка на адрес в куче (хипе). Когда объект ссылочного типа перестает использоваться, в дело вступает автоматический сборщик мусора: он видит, что на объект в хипе нету больше ссылок, условно удаляет этот объект и очищает память - фактически помечает, что данный сегмент памяти может быть использован для хранения других данных.
Так, в частности, если мы изменим метод Calculate следующим образом:
1 2 3 4 5 6 | static void Calculate(int t){ object x = 6; int y = 7; int z = y + t;} |
То теперь значение переменной x будет храниться в куче, так как она представляет ссылочный тип object, а в стеке будет храниться ссылка на объект в куче.

Составные типы
Теперь рассмотим ситуацию, когда тип значений и ссылочный тип представляют составные типы - структуру и класс:
1 2 3 4 5 6 7 8 9 10 11 12 13 | State state1 = new State(); // State - структура, ее данные размещены в стекеCountry country1 = new Country(); // Country - класс, в стек помещается ссылка на адрес в хипе // а в хипе располагаются все данные объекта country1struct State{ public int x; public int y;}class Country{ public int x; public int y;} |
Здесь в методе Main в стеке выделяется память для объекта state1. Далее в стеке создается ссылка для объекта country1 (Country country1), а с помощью вызова конструктора с ключевым словом new выделяется место в хипе (new Country()). Ссылка в стеке для объекта country1 будет представлять адрес на место в хипе, по которому размещен данный объект..

Таким образом, в стеке окажутся все поля структуры state1 и ссылка на объект country1 в хипе.
Но, допустим, в структуре State также определена переменная ссылочного типа Country. Где она будет хранить свое значение, если она определена в типе значений?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | State state1 = new State();Country country1 = new Country();struct State{ public int x; public int y; public Country country = new();}class Country{ public int x; public int y;} |
Значение переменной state1.country также будет храниться в куче, так как эта переменная представляет ссылочный тип:

Копирование значений
Тип данных надо учитывать при копировании значений. При присвоении данных объекту значимого типа он получает копию данных. При присвоении данных объекту ссылочного типа он получает не копию объекта, а ссылку на этот объект в хипе. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | State state1 = new State(); // Структура StateState state2 = new State();state2.x = 1;state2.y = 2;state1 = state2;state2.x = 5; // state1.x=1 по-прежнемуConsole.WriteLine(state1.x); // 1Console.WriteLine(state2.x); // 5Country country1 = new Country(); // Класс CountryCountry country2 = new Country();country2.x = 1;country2.y = 4;country1 = country2;country2.x = 7; // теперь и country1.x = 7, так как обе ссылки и country1 и country2 // указывают на один объект в хипеConsole.WriteLine(country1.x); // 7Console.WriteLine(country2.x); // 7 |
Так как state1 - структура, то при присвоении state1 = state2 она получает копию структуры state2. А объект класса country1 при присвоении country1 = country2; получает ссылку на тот же объект, на который указывает country2. Поэтому с изменением country2, так же будет меняться и country1.
Ссылочные типы внутри типов значений
Теперь рассмотрим более изощренный пример, когда внутри структуры у нас может быть переменная ссылочного типа, например, какого-нибудь класса:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | State state1 = new State();State state2 = new State();state2.country = new Country();state2.country.x = 5;state1 = state2;state2.country.x = 8; // теперь и state1.country.x=8, так как state1.country и state2.country // указывают на один объект в хипеConsole.WriteLine(state1.country.x); // 8Console.WriteLine(state2.country.x); // 8struct State{ public int x; public int y; public Country country = new(); // выделение памяти для объекта Country}class Country{ public int x; public int y;} |
Переменные ссылочных типов в структурах также сохраняют в стеке ссылку на объект в хипе. И при присвоении двух структур state1 = state2; структура state1 также получит ссылку на объект country в хипе. Поэтому изменение state2.country повлечет за собой также изменение state1.country.

Объекты классов как параметры методов
Организацию объектов в памяти следует учитывать при передаче параметров по значению и по ссылке. Если параметры методов представляют объекты классов, то использование параметров имеет некоторые особенности. Например, создадим метод, который в качестве параметра принимает объект Person:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | Person p = new Person { name = "Tom", age = 23 };ChangePerson(p);Console.WriteLine(p.name); // AliceConsole.WriteLine(p.age); // 23void ChangePerson(Person person){ // сработает person.name = "Alice"; // сработает только в рамках данного метода person = new Person { name = "Bill", age = 45 }; Console.WriteLine(person.name); // Bill}class Person{ public string name = ""; public int age;} |
При передаче объекта класса по значению в метод передается копия ссылки на объект. Эта копия указывает на тот же объект, что и исходная ссылка, потому мы можем изменить отдельные поля и свойства объекта, но не можем изменить сам объект. Поэтому в примере выше сработает только строка person.name = "Alice".
А другая строка person = new Person { name = "Bill", age = 45 } создаст новый объект в памяти, и person теперь будет указывать на новый объект в памяти. Даже если после этого мы его изменим, то это никак не повлияет на ссылку p в методе Main, поскольку ссылка p все еще указывает на старый объект в памяти.
Но при передаче параметра по ссылке (с помощью ключевого слова ref) в метод в качестве аргумента передается сама ссылка на объект в памяти. Поэтому можно изменить как поля и свойства объекта, так и сам объект:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Person p = new Person { name = "Tom", age = 23 };ChangePerson(ref p);Console.WriteLine(p.name); // BillConsole.WriteLine(p.age); // 45void ChangePerson(ref Person person){ // сработает person.name = "Alice"; // сработает person = new Person { name = "Bill", age = 45 };}class Person{ public string name = ""; public int age;} |
Операция new создаст новый объект в памяти, и теперь ссылка person (она же ссылка p из метода Main) будет указывать уже на новый объект в памяти.
Комментариев нет:
Отправить комментарий